/*	Copyright © 2007 Apple Inc. All Rights Reserved.
	
	Disclaimer: IMPORTANT:  This Apple software is supplied to you by 
			Apple Inc. ("Apple") in consideration of your agreement to the
			following terms, and your use, installation, modification or
			redistribution of this Apple software constitutes acceptance of these
			terms.  If you do not agree with these terms, please do not use,
			install, modify or redistribute this Apple software.
			
			In consideration of your agreement to abide by the following terms, and
			subject to these terms, Apple grants you a personal, non-exclusive
			license, under Apple's copyrights in this original Apple software (the
			"Apple Software"), to use, reproduce, modify and redistribute the Apple
			Software, with or without modifications, in source and/or binary forms;
			provided that if you redistribute the Apple Software in its entirety and
			without modifications, you must retain this notice and the following
			text and disclaimers in all such redistributions of the Apple Software. 
			Neither the name, trademarks, service marks or logos of Apple Inc. 
			may be used to endorse or promote products derived from the Apple
			Software without specific prior written permission from Apple.  Except
			as expressly stated in this notice, no other rights or licenses, express
			or implied, are granted by Apple herein, including but not limited to
			any patent rights that may be infringed by your derivative works or by
			other works in which the Apple Software may be incorporated.
			
			The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
			MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
			THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
			FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
			OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
			
			IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
			OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
			SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
			INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
			MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
			AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
			STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
			POSSIBILITY OF SUCH DAMAGE.
*/
#import "Scene.h"
#include <math.h>

#define DEG2RAD(x) (0.0174532925 * (x))
#define RAD2DEG(x) (57.295779578 * (x))

#define	kSquareSize				500		// needs to be the size of the custom NIB object
#define kSourceCircleRadius		10.0
#define kListenerCircleRadius	20.0
#define kDefaultDistance		175.0
#define NUM_BUFFERS_SOURCES		5		// this test app has 4 Source Objects and 4 Buffer Objects

#define kCaptureSamples				44100 * 5 // capture 5 seconds of data at 44k
#define kCaptureSourceIndex			4
#define	kCapturedAudioSampleRate	44100

#define kListenerIndex			5

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Globals
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ALCdevice	*gCaptureDevice = NULL;
UInt8*		gCaptureData = NULL;

float		gListenerPos[3] = {0.0,  0.0,  0.0};		// default position is centered
float		gListenerDirection = 0;
int			gSourceDirectionOnOff[NUM_BUFFERS_SOURCES] = {0, 0, 0, 0, 0};
float		gSourceAngle[NUM_BUFFERS_SOURCES] = {225, 315, 135, 45, 180}; // each source is now facing the center
float		gSourcePos[NUM_BUFFERS_SOURCES][3]  = {	{kDefaultDistance, 0.0, -kDefaultDistance},
														{kDefaultDistance, 0.0, kDefaultDistance},
														{-kDefaultDistance , 0.0, -kDefaultDistance},
														{-kDefaultDistance , 0.0, kDefaultDistance},
														{0.0 , 0.0, -kDefaultDistance}		}; 

char *	gSourceFile[NUM_BUFFERS_SOURCES - 1];			// only the 1st 4 sources use data from a file
ALuint	gBuffer[NUM_BUFFERS_SOURCES];
ALuint	gSource[NUM_BUFFERS_SOURCES];

float gSourceInnerConeAngle[NUM_BUFFERS_SOURCES] = {90, 90, 90, 90, 90};
float gSourceOuterConeAngle[NUM_BUFFERS_SOURCES] = {180, 180, 180, 180, 180};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Extension API Procs
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef ALvoid	AL_APIENTRY	(*alMacOSXRenderChannelCountProcPtr) (const ALint value);
ALvoid  alMacOSXRenderChannelCountProc(const ALint value)
{
	static	alMacOSXRenderChannelCountProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alMacOSXRenderChannelCountProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alMacOSXRenderChannelCount");
    }
    
    if (proc)
        proc(value);

    return;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef ALvoid	AL_APIENTRY	(*alcMacOSXRenderingQualityProcPtr) (const ALint value);
ALvoid  alcMacOSXRenderingQualityProc(const ALint value)
{
	static	alcMacOSXRenderingQualityProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alcMacOSXRenderingQualityProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXRenderingQuality");
    }
    
    if (proc)
        proc(value);

    return;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef ALvoid	AL_APIENTRY	(*alcMacOSXMixerOutputRateProcPtr) (const ALdouble value);
ALvoid  alcMacOSXMixerOutputRateProc(const ALdouble value)
{
	static	alcMacOSXMixerOutputRateProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alcMacOSXMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXMixerOutputRate");
    }
    
    if (proc)
        proc(value);

    return;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef ALdouble (*alcMacOSXGetMixerOutputRateProcPtr) ();
ALdouble  alcMacOSXGetMixerOutputRateProc()
{
	static	alcMacOSXGetMixerOutputRateProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alcMacOSXGetMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXGetMixerOutputRate");
    }
    
    if (proc)
        return proc();

    return 0.0;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef ALvoid	AL_APIENTRY	(*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq);
ALvoid  alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
{
	static	alBufferDataStaticProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic");
    }
    
    if (proc)
        proc(bid, format, data, size, freq);

    return;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef OSStatus	(*alcASASetSourceProcPtr)	(const ALuint property, ALuint source, ALvoid *data, ALuint dataSize);
OSStatus  alcASASetSourceProc(const ALuint property, ALuint source, ALvoid *data, ALuint dataSize)
{
    OSStatus	err = noErr;
	static	alcASASetSourceProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alcASASetSourceProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcASASetSource");
    }
    
    if (proc)
        err = proc(property, source, data, dataSize);
    return (err);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef OSStatus	(*alcASASetListenerProcPtr)	(const ALuint property, ALvoid *data, ALuint dataSize);
OSStatus  alcASASetListenerProc(const ALuint property, ALvoid *data, ALuint dataSize)
{
    OSStatus	err = noErr;
	static	alcASASetListenerProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alcASASetListenerProcPtr) alcGetProcAddress(NULL, "alcASASetListener");
    }
    
    if (proc)
        err = proc(property, data, dataSize);
    return (err);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef OSStatus	(*alcASAGetListenerProcPtr)	(const ALuint property, ALvoid *data, ALuint *dataSize);
OSStatus  alcASAGetListenerProc(const ALuint property, ALvoid *data, ALuint *dataSize)
{
    OSStatus	err = noErr;
	static	alcASAGetListenerProcPtr	proc = NULL;
    
    if (proc == NULL) {
        proc = (alcASAGetListenerProcPtr) alcGetProcAddress(NULL, "alcASAGetListener");
    }
    
    if (proc)
        err = proc(property, data, dataSize);
    return (err);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef ALCdevice*	(*alcCaptureOpenDeviceProcPtr)	(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize);
ALCdevice*  alcCaptureOpenDeviceProc(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize)
{
    ALCdevice*	device = NULL;
	device = alcCaptureOpenDevice (devicename, frequency, format, buffersize);
    return (device);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef void	(*alcCaptureStartProcPtr)	(ALCdevice*	device);
void  alcCaptureStartProc(ALCdevice* device)
{
	alcCaptureStart (device);	

    return;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef void	(*alcCaptureSamplesProcPtr)	(ALCdevice *device, ALCvoid *buffer, ALCsizei samples);
void  alcCaptureSamplesProc(ALCdevice *device, ALCvoid *buffer, ALCsizei samples)
{
	alcCaptureSamples (device, buffer, samples);	

    return;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initialize OpenAL -Get an Audio Device and Set Current OpenAL Context
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ALvoid*		gStaticBufferData = NULL;

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TeardownOpenAL()
{
    ALCcontext	*context = NULL;
    ALCdevice	*device = NULL;
	ALuint		returnedNames[NUM_BUFFERS_SOURCES];

	// Delete the Sources
    alDeleteSources(NUM_BUFFERS_SOURCES, returnedNames);
	// Delete the Buffers
    alDeleteBuffers(NUM_BUFFERS_SOURCES, returnedNames);
	
	//Get active context
    context = alcGetCurrentContext();
    //Get device for active context
    device = alcGetContextsDevice(context);
    //Release context
    alcDestroyContext(context);
    //Close device
    alcCloseDevice(device);
	if (gStaticBufferData)
		free(gStaticBufferData);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void	InitializeBuffers() 
{
	ALenum  error = AL_NO_ERROR;
	ALenum  format;
	ALvoid* data;
	ALsizei size;
	ALsizei freq;
	UInt32	i;
	
	// only the 1st 4 sources get data from a file. The 5th source gets data from capture
	for (i = 0; i < NUM_BUFFERS_SOURCES - 1; i ++)
	{	
		//Get the current path to the audio file (which is contained in the application bundle) 
		NSString* fileString = [[NSBundle mainBundle] pathForResource:[NSString stringWithCString:gSourceFile[i]] ofType:@"wav"];
		// get some audio data from a wave file
		CFURLRef fileURL = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)fileString, NULL);
		data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);
		
		CFRelease(fileURL);
		
		if((error = alGetError()) != AL_NO_ERROR) {
			printf("error loading %s: ", gSourceFile[i]);
			exit(1);
		}
		
		if (i == 0)
		{
			// use the static buffer data API once for testing
			gStaticBufferData = (ALvoid*)malloc(size);
			memcpy(gStaticBufferData, data, size);
			alBufferDataStaticProc(gBuffer[i], format, gStaticBufferData, size, freq);
		}
		else
		{
			// Attach Audio Data to OpenAL Buffer
			alBufferData(gBuffer[i], format, data, size, freq);
		}
		
		// Release the audio data
		free(data);
		
		if((error = alGetError()) != AL_NO_ERROR) {
			printf("error unloading %s: ", gSourceFile[i]);
		}	
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void InitializeSourcesAndPlay() 
{
	UInt32 i;
	ALenum error = AL_NO_ERROR;
	alGetError(); // Clear the error
    
	for (i = 0; i < NUM_BUFFERS_SOURCES; i++)
	{
		// Turn Looping ON
		alSourcei(gSource[i], AL_LOOPING, AL_TRUE);
		// Set Source Position
		alSourcefv(gSource[i], AL_POSITION, gSourcePos[i]);
		// Set Source Reference Distance
		alSourcef(gSource[i],AL_REFERENCE_DISTANCE, 5.0f);

		// only load data and start playing the non capture sources
		if (i < NUM_BUFFERS_SOURCES-1)
		{
			// attach OpenAL Buffer to OpenAL Source
			alSourcei(gSource[i], AL_BUFFER, gBuffer[i]);
			// Start Playing Sound
			alSourcePlay(gSource[i]);
		}
	}
			
	if((error = alGetError()) != AL_NO_ERROR) {
		printf("Error attaching buffer to source");
		exit(1);
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SCENE class
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@implementation Scene

- (id) init
{
	if((self = [super init])) {
		
		mHasASAExtension = false;
		mHasInput = false;
		
		//4 WAV files in the application bundle resources
		gSourceFile[0] = "sound_engine";
		gSourceFile[1] = "sound_voices";
		gSourceFile[2] = "sound_bubbles";
		gSourceFile[3] = "sound_electric";
	
		mCurrentObject = -1;
		mCenterOffset = kSquareSize/2.0;
		mVelocityScaler = 0.0;
		mAngle = 0.0;
		mListenerElevation = 0.0;

		mSourceOn[0] = 1;
		mSourceOn[1] = 1;
		mSourceOn[2] = 1;
		mSourceOn[3] = 1;
		mSourceOn[4] = 0; // capture

		mSourceDirection[0] = 0.0;
		mSourceDirection[1] = 0.0;
		mSourceDirection[2] = 0.0;
		mSourceDirection[3] = 0.0;
		mSourceDirection[4] = 0.0; // capture

		mSourceVelocityScaler[0] = 0.0;
		mSourceVelocityScaler[1] = 0.0;
		mSourceVelocityScaler[2] = 0.0;
		mSourceVelocityScaler[3] = 0.0;
		mSourceVelocityScaler[4] = 0.0; // capture

		mSourceOuterConeGain[0] = 0.0;
		mSourceOuterConeGain[1] = 0.0;
		mSourceOuterConeGain[2] = 0.0;
		mSourceOuterConeGain[3] = 0.0;
		mSourceOuterConeGain[4] = 0.0; // capture
		
		mSourceInnerConeAngle[0] = 90.0;
		mSourceInnerConeAngle[1] = 90.0;
		mSourceInnerConeAngle[2] = 90.0;
		mSourceInnerConeAngle[3] = 90.0;
		mSourceInnerConeAngle[4] = 90.0; // capture

		mSourceOuterConeAngle[0] = 180.0;
		mSourceOuterConeAngle[1] = 180.0;
		mSourceOuterConeAngle[2] = 180.0;
		mSourceOuterConeAngle[3] = 180.0;
		mSourceOuterConeAngle[4] = 180.0; // capture

		[self initOpenAL];
		[self setListenerOrientation:0 : NULL : NULL];
		[self setListenerVelocity:0 : NULL : NULL];
		[self setListenerGain:0.5];
	}
	return self;
}

- (void) dealloc {
	TeardownOpenAL();
	[super dealloc];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//draws a simple filled circle using quick glu calls
static void drawCircle(GLdouble x, GLdouble y, GLdouble r, GLfloat red, GLfloat grn, GLfloat blu){

	// A GRADIENT CIRCLE - looks better
	glPushMatrix();
		glTranslatef(x, y, 0.0);
		glBegin(GL_TRIANGLE_FAN);

			glColor3f(red, grn, blu);		
			glVertex3f(0.0, 0.0, 0.0);		
			{
				UInt32	i;
				float	x, y;
				float	stepAngle = 20;
				double	theAngle = -90;
				for (i = 0; i <= 18; i++)
				{
					glColor3f(red/2, grn/2, blu/2);		// gradient
					float	rads = DEG2RAD(theAngle);
					x = cos(rads) * r;
					y = sin(rads) * r;
					glVertex3f(x, y, 0.0);
					theAngle += stepAngle;
					glColor3f(red, grn, blu);			// gradient
				}
			}

		glEnd();
	glPopMatrix();
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) drawObjects {
		
	// layer the objects correctly
	if (mListenerElevation > 0)
	{
		[self drawSources];
		[self drawListener];
	}
	else
	{
		[self drawListener];
		[self drawSources];
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) drawListener {
	
	float	elevationSizeScaler = (abs(mListenerElevation) / 200.0)/2; // -1 to 1
	// translate that to .5 to 1.5
	if (mListenerElevation > 0)
		elevationSizeScaler = 1 + elevationSizeScaler;
	else
		elevationSizeScaler = 1 - elevationSizeScaler;
	
	// draw a triangle (nose)
	glPushMatrix();
		glTranslatef(gListenerPos[0] + mCenterOffset, -gListenerPos[2] + mCenterOffset, 0.0);
		//Rotate to show the listeners current orientation
		glRotatef(gListenerDirection, 0.0, 0.0, -1.0);
		glBegin(GL_TRIANGLES);
			glColor3f(0.0, 1.0, 0.0);
			glVertex3f(-6.0 * elevationSizeScaler, 0.0, 0.0);
			glVertex3f(6.0 * elevationSizeScaler, 0.0, 0.0);
			glColor3f(1.0, 0.0, 0.0);
			glVertex3f(0.0, 35.0 * elevationSizeScaler, 0.0);
		glEnd();
	glPopMatrix();


	// ears
	glPushMatrix();
		glTranslatef(gListenerPos[0] + mCenterOffset, -gListenerPos[2] + mCenterOffset, 0.0);
		//Rotate to show the listeners current orientation
		glRotatef(gListenerDirection, 0.0, 0.0, -1.0);
		glBegin(GL_QUADS);
			glColor3f(0.0, 0.0, 0.8);
			glVertex3f(-25.0 * elevationSizeScaler, 8.0 * elevationSizeScaler, 0.0);
			glVertex3f(-25.0 * elevationSizeScaler, -8.0 * elevationSizeScaler, 0.0);
			glVertex3f(24.5 * elevationSizeScaler, -8.0 * elevationSizeScaler, 0.0);
			glVertex3f(24.5 * elevationSizeScaler, 8.0 * elevationSizeScaler, 0.0);
		glEnd();
	glPopMatrix();
	
	// minor adjustments to position circle
	drawCircle(gListenerPos[0]- .5 + mCenterOffset, -gListenerPos[2] - 1 + mCenterOffset, 20 * elevationSizeScaler, 0 , 0, 1);

	if (mListenerElevation != 0.0)
		drawCircle(gListenerPos[0] + mCenterOffset, -gListenerPos[2] + mCenterOffset, 3, 0 , 0, .6);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#define	kArcSteps			16
#define	kInnerArcRadius		32	// larger but partially transparent
#define	kOuterArcRadius		26	// smaller with gradient

- (void) drawSourceWithDirection :(int)inTag
{
	
	glEnable(GL_BLEND); // blend the cones so both can be seen, top cone (INNER) is partially transparent
						// bottom cone (OUTER) has a gradient
				
	// CONE DRAWING - OUTER
	if (gSourceDirectionOnOff[inTag])
	{
		float	x, z;
		[self getSourceDirections:inTag : &x : &z];

		glPushMatrix();
			glTranslatef((gSourcePos[inTag][0] + mCenterOffset) + x, (-gSourcePos[inTag][2] + mCenterOffset) + z, 0.0);
			glRotatef(gSourceAngle[inTag], 0.0, 0.0, -1.0);
			glBegin(GL_TRIANGLE_FAN);
				glColor3f(0.0, 0.5, 0.0);		
				glVertex3f(0.0, 0.0, 0.0);		
				{
					UInt32	i;
					float	x, y;
					float	stepAngle = mSourceOuterConeAngle[inTag]/kArcSteps;
					double	theAngle = -(mSourceOuterConeAngle[inTag]/2) + 90;
					for (i = 0; i <= kArcSteps; i++)
					{
						glColor3f(0.0, 0.2, 0.0);		// gradient
						float	rads = DEG2RAD(theAngle);
						x = cos(rads) * kOuterArcRadius;
						y = sin(rads) * kOuterArcRadius;

						glVertex3f(x, y, 0.0);
						theAngle += stepAngle;
						glColor3f(0.0, 0.8, 0.0);		// gradient
					}
				}


			glEnd();
		glPopMatrix();
	}

	// CONE DRAWING - INNER
	if (gSourceDirectionOnOff[inTag])
	{
		float	x, z;
		[self getSourceDirections:inTag : &x : &z];

		glPushMatrix();
			glTranslatef((gSourcePos[inTag][0] + mCenterOffset) + x, (-gSourcePos[inTag][2] + mCenterOffset) + z, 0.0);
			glRotatef(gSourceAngle[inTag], 0.0, 0.0, -1.0);
			glBegin(GL_TRIANGLE_FAN);
				glColor4f(0.0, 1.0, 0.0, .4); // transparency (.4)
				glVertex3f(0.0, 0.0, 0.0);
				{
					UInt32	i;
					float	x, y;
					float	stepAngle = mSourceInnerConeAngle[inTag]/kArcSteps;
					double	theAngle = -(mSourceInnerConeAngle[inTag]/2) + 90;
					for (i = 0; i <= kArcSteps; i++)
					{
						float	rads = DEG2RAD(theAngle);
						x = cos(rads) * kInnerArcRadius;
						y = sin(rads) * kInnerArcRadius;
						glVertex3f(x, y, 0.0);
						theAngle += stepAngle;
					}
				}

			glEnd();
		glPopMatrix();
	}
	
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);			// blend the cones
	glDisable(GL_BLEND);										// blending is done

	// draw direction triangle if there is any velocity setting above zero
	if (mSourceVelocityScaler[inTag] > 0)
	{
		float	triangleLength = kSourceCircleRadius*2;
		triangleLength += (mSourceVelocityScaler[inTag]/686.0) * (kSourceCircleRadius*2);
		
		glPushMatrix();
			glTranslatef(gSourcePos[inTag][0] + mCenterOffset, -gSourcePos[inTag][2] + mCenterOffset, 0.0);
			glRotatef(gSourceAngle[inTag], 0.0, 0.0, -1.0);
			glBegin(GL_TRIANGLES);
				glColor3f(0.0, 1.0, 0.0);
				glVertex3f(-4.0, 0.0, 0.0);
				glVertex3f(4.0, 0.0, 0.0);
				glColor3f(1.0, 0.0, 0.0);
				glVertex3f(0.0, triangleLength, 0.0);
			glEnd();
		glPopMatrix();
	}
	
	//minor adjustments to position circle
	if (mSourceOn[inTag] == 0)
	{
		// draw a gray circle if source is not playing
		drawCircle(gSourcePos[inTag][0] + mCenterOffset, -gSourcePos[inTag][2] + mCenterOffset, kSourceCircleRadius, .5 , .5, .5); // capture circle is different color
	}
	else
	{
		if (inTag == kCaptureSourceIndex)
			drawCircle(gSourcePos[inTag][0] + mCenterOffset, -gSourcePos[inTag][2] + mCenterOffset, kSourceCircleRadius, 1 , 1, 0); // capture circle is different color
		else
			drawCircle(gSourcePos[inTag][0] + mCenterOffset, -gSourcePos[inTag][2] + mCenterOffset, kSourceCircleRadius, 1 , 0, 0);
	}

	if (gSourcePos[inTag][1] != 0.0)
		drawCircle(gSourcePos[inTag][0] + mCenterOffset, -gSourcePos[inTag][2]+ mCenterOffset, 3, .5 , 0, 0);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) drawSources
{
	//draw sources as 4 red circles
	[self drawSourceWithDirection: 0];
	[self drawSourceWithDirection: 1];
	[self drawSourceWithDirection: 2];
	[self drawSourceWithDirection: 3];

	[self drawSourceWithDirection: kCaptureSourceIndex]; // capture
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-(void) resetCurrentObject
{
	//set current object to -1 to designate no object selected.
	mCurrentObject = -1;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-(int) selectCurrentObject:(NSPoint *)point
{
	//find the object (circle) that contains the point
	//return -1 if none are found
	
	if(mCurrentObject != -1)
	  return mCurrentObject;
	  
	if([self pointInCircle:point x:gSourcePos[0][0] + mCenterOffset y:-gSourcePos[0][2] + mCenterOffset r:kSourceCircleRadius]){
		mCurrentObject = 0;
	}else if	([self pointInCircle:point x:gSourcePos[1][0] + mCenterOffset y:-gSourcePos[1][2] + mCenterOffset r:kSourceCircleRadius])
	    mCurrentObject = 1;
	else if	([self pointInCircle:point x:gSourcePos[2][0] + mCenterOffset y:-gSourcePos[2][2] + mCenterOffset r:kSourceCircleRadius])
	    mCurrentObject = 2;
	else if	([self pointInCircle:point x:gSourcePos[3][0] + mCenterOffset y:-gSourcePos[3][2] + mCenterOffset r:kSourceCircleRadius])
	    mCurrentObject = 3;	
	else if	([self pointInCircle:point x:gSourcePos[4][0] + mCenterOffset y:-gSourcePos[4][2] + mCenterOffset r:kSourceCircleRadius])
	    mCurrentObject = kCaptureSourceIndex;		// CAPTURE SOURCE
	else if	([self pointInCircle:point x:gListenerPos[0] + mCenterOffset y:-gListenerPos[2] + mCenterOffset r:kListenerCircleRadius])
	    mCurrentObject = kListenerIndex;
				
	return mCurrentObject;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// need to detect if point is in a source circle
// if so, return the source pos ID
// if not, return -1
- (bool) pointInCircle:(NSPoint *)point x:(float)x  y:(float)y  r:(float)r
{
   float x1 =point->x;
   float y1 = point->y;

   float dist  =0;
   //calculate distance from the center of the circle to the point clcked
   dist =(((x1 - x )*(x1 - x ))   +   ((y1 - y)*(y1 - y)));

	//Here we can test this for each of the sources
	
	//if the distance is less than the radius (squared), then the point is inside the circle
	if (dist <= r*r)
	  return TRUE;
	else
	 return FALSE;	
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-(void) setObjectPosition:(NSPoint *)point
{	
	if(mCurrentObject < kListenerIndex){
		[self setSourcePositionFromPoint:point];
	} 
	else if (mCurrentObject == kListenerIndex ) //listener
	{
		[self setListenerPosition:point];
	}
	
	// NOTIFY: Post a notification that object mCurrentObject has moved so the coordinate text can be updated
	[[NSNotificationCenter defaultCenter] postNotificationName: @"OALNotify" object: self];	
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) getCurrentObjectPosition:(int*)outCurObject : (float*) outX : (float*) outZ
{
	if(mCurrentObject < kListenerIndex){
		if(outX) *outX = gSourcePos[mCurrentObject][0];
		if(outX) *outZ = gSourcePos[mCurrentObject][2];
	} 
	else if (mCurrentObject == kListenerIndex ) //listener
	{
		if(outX) *outX = gListenerPos[0];
		if(outX) *outZ = gListenerPos[2];

	}
	if(outCurObject) *outCurObject = mCurrentObject;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) getObjectPosition:(int)inObject : (float*) outX : (float*) outZ;
{
	if(inObject < kListenerIndex){
		if(outX) *outX = gSourcePos[inObject][0];
		if(outX) *outZ = gSourcePos[inObject][2];
	} 
	else if (inObject == kListenerIndex ) //listener
	{
		if(outX) *outX = gListenerPos[0];
		if(outX) *outZ = gListenerPos[2];

	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initialize OpenAL Context, Buffers, Listener & Sources
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) initOpenAL {
		
	ALenum			error;
	ALCcontext		*newContext = NULL;
	ALCdevice		*newDevice = NULL;

	// Create a new OpenAL Device
	// Pass NULL to specify the systemâ€™s default output device
	newDevice = alcOpenDevice(NULL);
	if (newDevice != NULL)
	{
		// Create a new OpenAL Context
		// The new context will render to the OpenAL Device just created 
		newContext = alcCreateContext(newDevice, 0);
		if (newContext != NULL)
		{
			// Make the new context the Current OpenAL Context
			alcMakeContextCurrent(newContext);

			// Create some OpenAL Buffer Objects
			alGenBuffers(NUM_BUFFERS_SOURCES, gBuffer);
			if((error = alGetError()) != AL_NO_ERROR) {
				printf("Error Generating Buffers: ");
				exit(1);
			}

			// Create some OpenAL Source Objects
			alGenSources(NUM_BUFFERS_SOURCES, gSource);
			if(alGetError() != AL_NO_ERROR) 
			{
				printf("Error generating sources! \n");
				exit(1);
			}
		}
	}

	// Capture
	if (alcIsExtensionPresent( NULL, "ALC_EXT_CAPTURE" ))
	{
		// Setup a Capture Device
		gCaptureDevice = alcCaptureOpenDeviceProc( NULL, kCapturedAudioSampleRate, AL_FORMAT_MONO16, kCaptureSamples );
		if(gCaptureDevice)
		{
			// Capture is supported and there is an input device
			alcCaptureStartProc(gCaptureDevice);		

			mSourceOn[4] = 1; // capture
			mHasInput = true;
		}
		else
			mHasInput = false;	// Capture is supported but there is no input device
	}
	else
		mHasInput = false;	// Capture Not Supported by this OAL version
	
	// Reverb and Effects
	if (alcIsExtensionPresent( NULL, "ALC_EXT_ASA" ))
		mHasASAExtension = true;
	else
		mHasASAExtension = false;
		
	alGetError();
	
	InitializeBuffers();
	InitializeSourcesAndPlay();
}

- (bool) hasInput
{
	return mHasInput;
}

- (bool) hasASAExtension
{
	return mHasASAExtension;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Orientation
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setListenerOrientation: (float) angle : (float*) outX : (float*) outZ
{
	mAngle = angle;
	ALenum  error = AL_NO_ERROR;
	float	rads = DEG2RAD(mAngle);
	float	orientation[6] = {	0.0, 0.0, -1.0,    // direction
								0.0, 1.0, 0.0	}; //up	
								 
	orientation[0] = cos(rads);
	orientation[1] = sin(rads);		// No Change to the Z vector
	gListenerDirection = RAD2DEG(atan2(orientation[1], orientation[0]));
	
	// Change OpenAL Listener's Orientation
	orientation[0] = sin(rads);
	orientation[1] = 0.0;			// No Change to the Y vector
	orientation[2] = -cos(rads);	

	alListenerfv(AL_ORIENTATION, orientation);
	if((error = alGetError()) != AL_NO_ERROR)
		printf("Error Setting Listener Orientation");
		
	// set the listener velocity as well, in this app we are always syncing the velocity to the direction that the listener is facing
	[self setListenerVelocity: mVelocityScaler : outX : outZ];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Listener Velocity Gain
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setListenerVelocity:(float) inVelocity : (float*) outX : (float*) outZ
{
	mVelocityScaler = inVelocity;
	
	ALenum  error = AL_NO_ERROR;
	float	rads = DEG2RAD(mAngle);
	float	velocity[3] = {	0.0, 0.0, 0.0}; //up	
								 	
	// Change OpenAL Listener's Orientation
	velocity[0] = sin(rads) * mVelocityScaler;
	velocity[1] = 0.0;			// No Change to the Y vector
	velocity[2] = -cos(rads) * mVelocityScaler;
	
	if (outX) *outX = velocity[0];	
	if (outZ) *outZ = velocity[2];	

	alListenerfv(AL_VELOCITY, velocity);
	if((error = alGetError()) != AL_NO_ERROR)
		printf("Error Setting Listener Velocity");

}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Position
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void)setSourcePositionFromPoint:(NSPoint *)point
{
	gSourcePos[mCurrentObject][0] = point->x - mCenterOffset;
	gSourcePos[mCurrentObject][2] = -point->y + mCenterOffset;	// top view only in this demo!
	
	alSourcefv(gSource[mCurrentObject], AL_POSITION, gSourcePos[mCurrentObject]);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void)setSourcePositionX:(int) inTag : (float)inX
{
	gSourcePos[inTag][0] = inX;
	alSourcefv(gSource[inTag], AL_POSITION, gSourcePos[inTag]);
	[self drawSourceWithDirection :inTag];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void)setSourcePositionY:(int) inTag : (float)inY
{
	gSourcePos[inTag][1] = inY;
	alSourcefv(gSource[inTag], AL_POSITION, gSourcePos[inTag]);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void)setSourcePositionZ:(int) inTag : (float)inZ
{
	gSourcePos[inTag][2] = inZ;
	alSourcefv(gSource[inTag], AL_POSITION, gSourcePos[inTag]);
	[self drawSourceWithDirection :inTag];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setListenerPosition:(NSPoint *) point
{
	gListenerPos[0] = point->x - mCenterOffset;
	gListenerPos[1] = mListenerElevation;
	gListenerPos[2] = -point->y + mCenterOffset;				// top view only in this demo!

	alListenerfv(AL_POSITION, gListenerPos);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setListenerElevation: (float)elevation
{
	mListenerElevation = elevation;
	
	gListenerPos[1] = mListenerElevation;
	// do not change x or z 
	
	alListenerfv(AL_POSITION, gListenerPos);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void)setListenerPositionX: (float)inX
{
	gListenerPos[0] = inX;
	alListenerfv(AL_POSITION, gListenerPos);
	[self drawListener];
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void)setListenerPositionZ: (float)inX
{
	gListenerPos[2] = inX;
	alListenerfv(AL_POSITION, gListenerPos);
	[self drawListener];
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourcePlayState:(int)inTag :(int)inCheckBoxValue
{
	if (mSourceOn[inTag] == inCheckBoxValue)
		return;
	
	mSourceOn[inTag] = inCheckBoxValue;
	
	if (mSourceOn[inTag])
		alSourcePlay(gSource[inTag]);
	else
		alSourceStop(gSource[inTag]);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  Pitch
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourcePitch:(int)inTag :(float)inPitch
{
	alSourcef(gSource[inTag], AL_PITCH, inPitch);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Gain
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceGain:(int)inTag :(float)inGain
{
	alSourcef(gSource[inTag], AL_GAIN, inGain);
	// alSourcef (gSource[inTag], AL_SEC_OFFSET, 1.0); // quick way to test the set offset API until controls can be added to UI
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Rolloff
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceRolloffFactor:(int)inTag :(float)inRolloff
{
	alSourcef(gSource[inTag], AL_ROLLOFF_FACTOR, inRolloff);
}

- (float) getSourceRolloffFactor:(int)inTag
{
	float	ro;
	alGetSourcef(gSource[inTag], AL_ROLLOFF_FACTOR, &ro);
	return ro;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Reference Distance
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceReferenceDistance:(int)inTag :(float)inReferenceDistance
{
	alSourcef(gSource[inTag], AL_REFERENCE_DISTANCE, inReferenceDistance);
}

- (float) getSourceReferenceDistance:(int)inTag
{
	float	rd;
	alGetSourcef(gSource[inTag], AL_REFERENCE_DISTANCE, &rd);
	return rd;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Max Distance
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceMaxDistance:(int)inTag :(float)inMaxDistance
{
	alSourcef(gSource[inTag], AL_MAX_DISTANCE, inMaxDistance);
}

- (float) getSourceMaxDistance:(int)inTag
{
	float	md;
	alGetSourcef(gSource[inTag], AL_MAX_DISTANCE, &md);
	return md;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Direction & Velocity
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceVelocity:(int)inTag :(float) inVelocity
{
	mSourceVelocityScaler[inTag] = inVelocity;

	float	velocities[3];
	velocities[1] = 0;	// No Change to the Y vector
	[self getSourceVelocities: inTag : &velocities[0] : &velocities[2]];
	
	alSourcefv(gSource[inTag], AL_VELOCITY, velocities);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceDirection:(int)inTag
{
	float	directions[3];
	directions[1] = 0;		// No Change to the Y vector
	
	if (gSourceDirectionOnOff[inTag] == false)
	{
		directions[0] = 0;
		directions[2] = 0;
		alSourcefv(gSource[inTag], AL_DIRECTION, directions);
		
		alSourcef(gSource[inTag], AL_CONE_INNER_ANGLE, 360.0);
		alSourcef(gSource[inTag], AL_CONE_OUTER_ANGLE, 360.0);
		alSourcef(gSource[inTag], AL_CONE_OUTER_GAIN, 0.0);
	}
	else
	{
		[self getSourceDirections: inTag : &directions[0] : &directions[2]];
		
		alSourcefv(gSource[inTag], AL_DIRECTION, directions);
		alSourcef(gSource[inTag], AL_CONE_INNER_ANGLE, 90	);
		alSourcef(gSource[inTag], AL_CONE_OUTER_ANGLE, 180);
		alSourcef(gSource[inTag], AL_CONE_OUTER_GAIN, mSourceOuterConeGain[inTag]	);
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceOuterConeGain:(int)inTag :(float) inGain
{
	mSourceOuterConeGain[inTag] = inGain;
	
	alSourcef(gSource[inTag], AL_CONE_OUTER_GAIN, mSourceOuterConeGain[inTag]);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceInnerConeAngle:(int)inTag :(float) inAngle
{
	mSourceInnerConeAngle[inTag] = inAngle;
	
	alSourcef(gSource[inTag], AL_CONE_INNER_ANGLE, mSourceInnerConeAngle[inTag]);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceOuterConeAngle:(int)inTag :(float) inAngle
{
	mSourceOuterConeAngle[inTag] = inAngle;
	
	alSourcef(gSource[inTag], AL_CONE_OUTER_ANGLE, mSourceOuterConeAngle[inTag]);
	[self drawSourceWithDirection :inTag];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceDirectionOnOff:(int)inTag :(int)inCheckBoxValue
{
	if (gSourceDirectionOnOff[inTag] == inCheckBoxValue)
		return;
	
	gSourceDirectionOnOff[inTag] = inCheckBoxValue;
	[self setSourceDirection: inTag];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceAngle:(int)inTag :(float)inAngle
{
	gSourceAngle[inTag] = inAngle;

	[self setSourceVelocity: inTag : mSourceVelocityScaler[inTag]];
	[self setSourceDirection: inTag];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) getSourceVelocities:(int)inTag : (float*) outX : (float*) outZ
{
	float	rads = DEG2RAD(gSourceAngle[inTag]);
									 
	if (outX) *outX = sin(rads) * mSourceVelocityScaler[inTag];
	if (outZ) *outZ = -cos(rads) * mSourceVelocityScaler[inTag];
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) getSourceDirections:(int)inTag : (float*) outX : (float*) outZ
{
	float	rads = DEG2RAD(gSourceAngle[inTag]);
									 
	if (outX) *outX = sin(rads);		
	if (outZ) *outZ = -cos(rads);
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Listener Gain
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setListenerGain:(float)inGain
{
	alListenerf(AL_GAIN, inGain);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Render Channels
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setRenderChannels:(int)inCheckBoxValue
{
	// Global Setting:
	// Used to Force OpenAL to render to stereo, even if the user's default audio hw is multichannel

	UInt32		setting = (inCheckBoxValue == 0) ? alcGetEnumValue(NULL, "ALC_RENDER_CHANNEL_COUNT_MULTICHANNEL") : alcGetEnumValue(NULL, "ALC_RENDER_CHANNEL_COUNT_STEREO");
	
	alMacOSXRenderChannelCountProc((const ALint) setting);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Render Quality
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setRenderQuality:(int)inCheckBoxValue
{
	// Global Setting:
	// Used to turn on HRTF Rendering when OpenAL is rendering to stereo
	
	UInt32		setting = (inCheckBoxValue == 0) ? alcGetEnumValue(NULL, "ALC_SPATIAL_RENDERING_QUALITY_LOW") : alcGetEnumValue(NULL, "ALC_SPATIAL_RENDERING_QUALITY_HIGH");
	
	alcMacOSXRenderingQualityProc((const ALint) setting);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Reverb Level
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceReverb:(int)inTag :(float)inReverbSendLevel
{
	ALfloat		level = inReverbSendLevel;
	alcASASetSourceProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_SEND_LEVEL"), gSource[inTag], &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Reverb Level
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceOcclusion:(int)inTag :(float)inLevel
{
	ALfloat		level = inLevel;
	alcASASetSourceProc(alcGetEnumValue(NULL, "ALC_ASA_OCCLUSION"), gSource[inTag], &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Source Reverb Level
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSourceObstruction:(int)inTag :(float)inReverbSendLevel
{
	ALfloat		level = inReverbSendLevel;
	alcASASetSourceProc(alcGetEnumValue(NULL, "ALC_ASA_OBSTRUCTION"), gSource[inTag], &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Global Reverb Level
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setGlobalReverb :(float)inReverbLevel
{
	ALfloat		level = inReverbLevel;
	alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_GLOBAL_LEVEL"), &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Reverb ON
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setReverbOn:(int)inCheckBoxValue
{
	UInt32		setting = (inCheckBoxValue == 0) ? 0 : 1;
	alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_ON"), &setting, sizeof(setting));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setReverbEQGain:(float)inLevel
{
	ALfloat		level = inLevel;
	alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_EQ_GAIN"), &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setReverbEQBandwidth:(float)inLevel
{
	ALfloat		level = inLevel;
	alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_EQ_BANDWITH"), (ALvoid *) &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setReverbEQFrequency:(float)inLevel
{
	ALfloat		level = inLevel;
	alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_EQ_FREQ"), &level, sizeof(level));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setReverbRoomType:(int)inTag controlIndex:(int) inIndex title:(NSString*) inTitle
{
	UInt32		roomtype = inTag;
	UInt32		roomIndex = inIndex;
	if (roomIndex < 12)
	{
		// the 1st 12 menu items have the proper reverb constant in the tag value
		alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_ROOM_TYPE"), &roomtype, sizeof(roomtype));
	}
	else
	{
		const char *fullPathToFile;
		fullPathToFile =[[[NSBundle mainBundle] pathForResource:inTitle ofType:@"aupreset" inDirectory:[NSString stringWithCString:"ReverbPresets"]]UTF8String];

		alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_PRESET"), (void *) fullPathToFile, strlen(fullPathToFile));
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setReverbQuality:(int)inTag
{
	UInt32		quality = inTag;
	alcASASetListenerProc(alcGetEnumValue(NULL, "ALC_ASA_REVERB_QUALITY"), &quality, sizeof(quality));
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Distance Model
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setDistanceModel:(int)inTag
{
	alDistanceModel(inTag);	
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Doppler Factor
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setDopplerFactor :(float)inValue
{
	alDopplerFactor(inValue);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Speed Of Sound
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) setSpeedOfSound :(float)inValue
{
	alSpeedOfSound(inValue);
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CAPTURE
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- (void) captureSamples :(int*)outValue
{
	if (!gCaptureData)
	{
		gCaptureData = (UInt8*) calloc(1, kCaptureSamples * 2); // 1 second of 44k mono 16 bit data
	}
	
	alGetError(); // reset

	alcGetIntegerv( gCaptureDevice, ALC_CAPTURE_SAMPLES, 1, outValue );

	// get some new sample, free gCaptureData if this fails
	alcCaptureSamplesProc( gCaptureDevice, gCaptureData, *outValue );

	UInt32	err = alGetError();
	if (err)
		return;

	alSourceStop(gSource[4]);
	alSourcei(gSource[4], AL_BUFFER, AL_NONE);
	
	ALint	count = 1;
	while (count > 0)
		alGetSourcei(gSource[4], AL_BUFFERS_QUEUED, &count);

	alBufferData(gBuffer[4], AL_FORMAT_MONO16, gCaptureData, *outValue * 2, kCapturedAudioSampleRate);
	alSourcei(gSource[4], AL_BUFFER, gBuffer[4]);
	alSourcePlay(gSource[4]);
}

@end