/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// snd_fmod.c -- all code for the fmod based sound system (By Robert 'Heffo' Heffernan)

#include "quakedef.h"

#ifdef _WIN32
#include "winquake.h"
#endif

/*////////////////////////////////////////////////////////////////////////////////////
//                         All public vars here for no screwups                     //
////////////////////////////////////////////////////////////////////////////////////*/
SND_OpenFiles_t	SND_Files[32];

int				SND_Initialised;
int				SND_HardwareChannels;
int				SND_SoftwareChannels;
int				SND_TopUsedChannel;
int				SND_FakeDolbyEnabled;
float			SND_ActualVolume;
float			SND_ActualMusicVolume;

cvar_t			SND_MusicVolume		= {"SND_MusicVolume"	, "1", true};
cvar_t			SND_Volume			= {"SND_Volume"			, "0.7", true};
cvar_t			SND_NoSound			= {"SND_NoSound"		, "0"};
cvar_t			SND_NoMusic			= {"SND_NoMusic"		, "0"};
cvar_t			SND_NoAmbient		= {"SND_NoAmbient"		, "0"};
cvar_t			SND_NoWaterEffect	= {"SND_NoWaterEffect"	, "0"}; //Heffo - Extension #1 - Underwater Pitch Shifting
cvar_t			SND_NoSlowmoEffect	= {"SND_NoSlowmoEffect"	, "0"};
cvar_t			SND_WaterPitch		= {"SND_WaterPitch"		, "0.8"}; //Heffo - Extension #1 - Underwater Pitch Shifting
cvar_t			SND_Samplerate		= {"SND_Samplerate"		, "11025", true}; /* 11025, 22050, 44100, 48000 */
cvar_t			SND_AudioDriver		= {"SND_AudioDriver"	, "2", true}; /* 0 = No Sound, 1 = WINMM, 2 = DSound, 3 = A3D */
cvar_t			SND_Performance		= {"SND_Performance"	, "0", true}; /* 0 = Speed, 1 = Quality */
cvar_t			SND_NoHardware3D	= {"SND_NoHardware3D"	, "0", true};
cvar_t			SND_8bitOnly		= {"SND_8bitOnly"		, "0", true};
cvar_t			SND_FakeDolby		= {"SND_FakeDolby"		, "0", true};
cvar_t			SND_AmbientLevel	= {"SND_AmbientLevel"	, "0.3"};
cvar_t			SND_AmbientFade		= {"SND_AmbientFade"	, "100"};
cvar_t			SND_QuietLevel		= {"SND_QuietLevel"		, "8"};
cvar_t			SND_ChanStats		= {"SND_ChanStats"		, "0"};

SND_Sample_t	SND_Samples[MAX_SFX];
SND_Sample_t	*SND_AmbientSamples[2];
char			OutputAPI[4][32];

SND_Channels_t	SND_Channel[MAX_CHANNELS];
SND_Channels_t	SND_StaticChannels[MAX_STATICFX];
SND_Channels_t	*SND_AmbientSfx[2];
vec3_t			SND_ListenerOrigin;
vec3_t			SND_ListenerForward;
vec3_t			SND_ListenerUp;
vec3_t			SND_ListenerRight;
int				SND_IsBlocked = 0;
int				SND_LastContent = CONTENTS_EMPTY;	//Heffo - Extension #1 - Underwater Pitch Shifting
int				SND_WaterEffectDisabled;			//Heffo - Extension #1 - Underwater Pitch Shifting

FSOUND_STREAM	*SND_MusicStream;
SND_Sample_t	SND_FakeMusicSample;
SND_Channels_t	*SND_MusicChannel;
int				SND_MusicPaused;
int				SND_MusicLooping;

SND_LoopInfo_t	SND_LoopPoints;

// Gross hack to read bodgie id wav files
byte			*data_p;
byte 			*iff_end;
byte 			*last_chunk;
byte 			*iff_data;
int 			iff_chunk_len;


short GetLittleShort(void)
{
	short val = 0;
	val = *data_p;
	val = val + (*(data_p+1)<<8);
	data_p += 2;
	return val;
}

int GetLittleLong(void)
{
	int val = 0;
	val = *data_p;
	val = val + (*(data_p+1)<<8);
	val = val + (*(data_p+2)<<16);
	val = val + (*(data_p+3)<<24);
	data_p += 4;
	return val;
}

void FindNextChunk(char *name)
{
	while (1)
	{
		data_p=last_chunk;

		if (data_p >= iff_end)
		{	// didn't find the chunk
			data_p = NULL;
			return;
		}
		
		data_p += 4;
		iff_chunk_len = GetLittleLong();
		if (iff_chunk_len < 0)
		{
			data_p = NULL;
			return;
		}

		data_p -= 8;
		last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
		if (!Q_strncmp(data_p, name, 4))
			return;
	}
}

void FindChunk(char *name)
{
	last_chunk = iff_data;
	FindNextChunk (name);
}


void DumpChunks(void)
{
	char	str[5];
	
	str[4] = 0;
	data_p=iff_data;
	do
	{
		memcpy (str, data_p, 4);
		data_p += 4;
		iff_chunk_len = GetLittleLong();
		Con_Printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
		data_p += (iff_chunk_len + 1) & ~1;
	} while (data_p < iff_end);
}

/*
============
GetLoopInfo
============
*/
void GetLoopInfo (char *name, byte *wav, int wavlength)
{
	int     format;
	int		dump;

	if (!wav)
		return;

	iff_data = wav;
	iff_end = wav + wavlength;

// find "RIFF" chunk
	FindChunk("RIFF");
	if (!(data_p && !Q_strncmp(data_p+8, "WAVE", 4)))
	{
		Con_Printf("Missing RIFF/WAVE chunks\n");
		return;
	}

// get "fmt " chunk
	iff_data = data_p + 12;
// DumpChunks ();

	FindChunk("fmt ");
	if (!data_p)
	{
		Con_Printf("Missing fmt chunk\n");
		return;
	}
	data_p += 8;
	format = GetLittleShort();
	if (format != 1)
	{
		Con_Printf("Microsoft PCM format only\n");
		return;
	}

	dump = GetLittleShort();
	dump = GetLittleLong();
	data_p += 4+2;
	dump = GetLittleShort() * 0.125;	// Tomaz - Speed

// get cue chunk
	FindChunk("cue ");
	if (data_p)
	{
		data_p += 32;
		SND_LoopPoints.loopstart = GetLittleLong();
//		Con_Printf("loopstart=%d\n", sfx->loopstart);

	// if the next chunk is a LIST chunk, look for a cue length marker
		FindNextChunk ("LIST");
		if (data_p)
		{
			if (!strncmp (data_p + 28, "mark", 4))
			{	// this is not a proper parse, but it works with cooledit...
				data_p += 24;
				SND_LoopPoints.looplength = GetLittleLong ();	// samples in loop
//				Con_Printf("looped length: %i\n", i);
			}
		}
	}
	else
	{
		SND_LoopPoints.loopstart = -1;
		SND_LoopPoints.looplength = -1;
	}
}
// Gross hack to read bodgie id wav files

/*////////////////////////////////////////////////////////////////////////////////////
//                  Custom Fmod file routines to allow PAK file access              //
////////////////////////////////////////////////////////////////////////////////////*/
char *COM_FileExtension (char *in);
unsigned int SND_FOpen (const char *name)
{
	int		i, j;
	int		fi;

	for(i=0;i<64;i++)
	{
		if(!SND_Files[i].length)
		{
			j = COM_OpenFile ((char *)name, &fi);

			if(j == -1)
			{
				// DISABLED - i hate these warning msg's =)
//				Con_SafePrintf("SND_FOpen: Failed to open %s, file not found\n", name);
				return 0;
			}

			SND_Files[i].data = malloc(j);
			SND_Files[i].length = j;
			SND_Files[i].position = 0;
			strcpy(SND_Files[i].filename, name);

			Sys_FileRead(fi, SND_Files[i].data, j);
			COM_CloseFile(fi);

			if (!strcmp(COM_FileExtension(SND_Files[i].filename), "wav"))
			{
				GetLoopInfo(SND_Files[i].filename, SND_Files[i].data, SND_Files[i].length);
			}

			Con_DPrintf("SND_Fopen: Sucessfully opened %s\n", name);
			return i+1;
		}
	}

	Con_SafePrintf("SND_FOpen: Failed to open %s, insufficient handles\n", name);
	return 0;
}

void SND_FClose (unsigned int handle)
{
	int i;
	
	i = handle - 1;

	SND_Files[i].length = 0;
	SND_Files[i].position = 0;
	strcpy(SND_Files[i].filename, "\0");
	free(SND_Files[i].data);
}

int SND_FRead (void *buffer, int size, unsigned int handle)
{
	int i, read;

	i = handle - 1;

	if((SND_Files[i].position + size) > SND_Files[i].length)
	{
		read = SND_Files[i].length - SND_Files[i].position;

		if(read <= 0)
		{
			return 0;
		}
	}
	else
	{
		read = size;
	}

	memcpy(buffer, SND_Files[i].data + SND_Files[i].position, read);

	SND_Files[i].position += read;

	return read;
}

int SND_FSeek (unsigned int handle, int pos, signed char mode)
{
	int i;

	i = handle - 1;

	switch(mode)
	{
	case SEEK_END:
		SND_Files[i].position = SND_Files[i].length + pos;
		break;
	case SEEK_CUR:
		SND_Files[i].position += pos;
		break;
	case SEEK_SET:
		SND_Files[i].position = 0 + pos;
		break;
	}

	return 1;
}

int SND_FTell (unsigned int handle)
{
	int i;

	i = handle - 1;

	return SND_Files[i].position;
}


/*////////////////////////////////////////////////////////////////////////////////////
//                           Quake Sound Initialisation                             //
////////////////////////////////////////////////////////////////////////////////////*/

extern cvar_t	samplerate;

void S_Restart (void)
{
	int i, fval;
	unsigned int m;
	char trackname[MAX_QPATH];
	int trackpos;

	//Save Music Info
	if(SND_MusicStream)
	{
		strcpy(trackname, SND_MusicChannel->sample->name);
		trackpos = FSOUND_Stream_GetTime(SND_MusicStream);
		S_StopMusic();
	}

	S_Shutdown();

	//Setup Performance
	if(SND_Performance.value)
	{
		fval = FSOUND_SetMixer(FSOUND_MIXER_QUALITY_AUTODETECT);
	}
	else
	{
		fval = FSOUND_SetMixer(FSOUND_MIXER_AUTODETECT);
	}

	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed, couldn't set mixer\n");
		SND_Initialised = 0;
		return;
	}

	//Setup Driver
	switch((int)SND_AudioDriver.value)
	{
	case 0:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_NOSOUND);
		break;
	case 1:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_WINMM);
		break;
	case 2:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
		break;
	case 3:
		fval = FSOUND_SetOutput(FSOUND_OUTPUT_A3D);
		break;
	}

	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed, couldn't set driver\n");
		SND_Initialised = 0;
		return;
	}

	if(!SND_NoHardware3D.value)
	{
		SND_HardwareChannels = FSOUND_GetNumHardwareChannels();
	}
	else
	{
		SND_HardwareChannels = 0;
	}

	SND_SoftwareChannels = MAX_CHANNELS - SND_HardwareChannels;

	fval = FSOUND_Init(SND_Samplerate.value, SND_SoftwareChannels, 0);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed\n");
		SND_Initialised = 0;
		return;
	}

	FSOUND_SetSFXMasterVolume(255);

	//Reload Precached Sounds
	if(SND_8bitOnly.value)
	{
		m = (FSOUND_LOOP_OFF | FSOUND_8BITS | FSOUND_MONO);
	}
	else
	{
		m = (FSOUND_LOOP_OFF | FSOUND_16BITS | FSOUND_MONO);
	}

	for(i=0;i<MAX_SFX;i++)
	{
		if(SND_Samples[i].name[0] != '\0')
		{
			SND_Samples[i].sample = FSOUND_Sample_Load(FSOUND_FREE, SND_Samples[i].name, m, 0);

			
			if(!SND_Samples[i].loops)
			{
				FSOUND_Sample_SetLoopMode(SND_Samples[i].sample, FSOUND_LOOP_OFF);
			}
			else
			{
				FSOUND_Sample_SetLoopPoints(SND_Samples[i].sample, SND_Samples[i].loopstart, SND_Samples[i].loopstop);
				FSOUND_Sample_SetLoopMode(SND_Samples[i].sample, FSOUND_LOOP_NORMAL);
			}
		}
	}

	SND_Initialised = 1;

	//Restart Music if Needed
	if(trackpos)
	{
		S_StartMusic(trackname, SND_MusicLooping);
		FSOUND_Stream_SetTime(SND_MusicStream, trackpos);
	}

	//Restart Ambients (this is easy ;)
	SND_AmbientSfx[0]->inuse = false;
	SND_AmbientSfx[1]->inuse = false;

	//Restart Statics
	if(cl.worldmodel)
	{
		for(i=0;i<MAX_CHANNELS;i++)
		{
			if(!SND_Channel[i].is_static)
				continue;

			SND_Channel[i].channel = FSOUND_PlaySound3DAttrib(SND_Channel[i].channel, SND_Channel[i].sample->sample, -1, SND_Channel[i].volume, SND_Channel[i].pan, NULL, NULL);
		}
	}

	//Set FakeSurround up again
	if(SND_FakeDolbyEnabled)
	{
		FSOUND_SetSurround(FSOUND_ALL, true);
	}
	else
	{
		FSOUND_SetSurround(FSOUND_ALL, false);
	}

	if (SND_Samplerate.value == 11025)
		Cvar_Set("samplerate", "0");
	if (SND_Samplerate.value == 22050)
		Cvar_Set("samplerate", "1");
	if (SND_Samplerate.value == 44100)
		Cvar_Set("samplerate", "2");
}

void S_Init (void)
{
	int		fval, i;
	float	fver;

	Con_Printf("\nSound Initialization\n");

	Cmd_AddCommand("s_restart", S_Restart);
	Cmd_AddCommand("s_playmusic", S_StartMusicConsole);
	Cmd_AddCommand("s_stopmusic", S_StopMusic);
	Cmd_AddCommand("s_pausemusic", S_PauseMusic);
	Cmd_AddCommand("s_resumemusic", S_ResumeMusic);
	Cmd_AddCommand("s_channelreport", S_ChannelReport);
	Cmd_AddCommand("s_samplereport", S_SampleReport);
	Cmd_AddCommand("play", S_Play);
	Cmd_AddCommand("playvol", S_PlayVol);

	Cvar_RegisterVariable(&SND_MusicVolume);
	Cvar_RegisterVariable(&SND_Volume);
	Cvar_RegisterVariable(&SND_NoSound);
	Cvar_RegisterVariable(&SND_NoMusic);
	Cvar_RegisterVariable(&SND_NoAmbient);
	Cvar_RegisterVariable(&SND_NoWaterEffect); //Heffo - Extension #1 - Underwater Pitch Shifting
	Cvar_RegisterVariable(&SND_NoSlowmoEffect);
	Cvar_RegisterVariable(&SND_WaterPitch); //Heffo - Extension #1 - Underwater Pitch Shifting
	Cvar_RegisterVariable(&SND_Samplerate);
	Cvar_RegisterVariable(&SND_AudioDriver);
	Cvar_RegisterVariable(&SND_Performance);
	Cvar_RegisterVariable(&SND_NoHardware3D);
	Cvar_RegisterVariable(&SND_8bitOnly);
	Cvar_RegisterVariable(&SND_FakeDolby);
	Cvar_RegisterVariable(&SND_ChanStats);
	Cvar_RegisterVariable(&SND_AmbientLevel);
	Cvar_RegisterVariable(&SND_AmbientFade);
	Cvar_RegisterVariable(&SND_QuietLevel);

	if (COM_CheckParm("-nosound"))
	{
		SND_Initialised = 0;
		return;
	}

	if (COM_CheckParm("-samplerate"))
	{
		Cvar_SetValue("SND_Samplerate", Q_atof(com_argv[COM_CheckParm("-samplerate")+1]));
	}

	fver = FSOUND_GetVersion();
	if(fver < FMOD_VERSION)
	{
		Con_Printf("\nFMOD DLL version incorrect, found v%1.2f, requires v%1.2f or newer\n", fver, FMOD_VERSION);
		SND_Initialised = 0;
		return;
	}

	fval = FSOUND_SetMixer(FSOUND_MIXER_QUALITY_AUTODETECT);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed, couldn't set mixer\n");
		SND_Initialised = 0;
		return;
	}

	// You may wonder why I initalize twice... so I can get the number of hardware channels and
	// then only allocate the minimum number of software channels required for MAX_CHANNELS
	fval = FSOUND_Init(SND_Samplerate.value, MAX_CHANNELS, 0);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed\n");
		SND_Initialised = 0;
		return;
	}

	SND_HardwareChannels = FSOUND_GetNumHardwareChannels();
	SND_SoftwareChannels = MAX_CHANNELS - SND_HardwareChannels;

	FSOUND_Close();

	fval = FSOUND_Init(SND_Samplerate.value, SND_SoftwareChannels, 0);
	if(!fval)
	{
		Con_Printf("\nFMOD initialization failed\n");
		SND_Initialised = 0;
		return;
	}

	FSOUND_SetSFXMasterVolume(255);

	FSOUND_File_SetCallbacks(&SND_FOpen, &SND_FClose, &SND_FRead, &SND_FSeek, &SND_FTell);

	for(i=0;i<MAX_SFX;i++)
	{
		SND_Samples[i].sample = NULL;
		strcpy(SND_Samples[i].name, "\0");
	}
	
	for(i=0;i<MAX_CHANNELS;i++)
	{
		SND_Channel[i].attn = 0;
		SND_Channel[i].chan = 0;
		SND_Channel[i].channel = i;
		SND_Channel[i].ent = -2;
		SND_Channel[i].inuse = false;
		SND_Channel[i].origin[0] = 0;
		SND_Channel[i].origin[1] = 0;
		SND_Channel[i].origin[2] = 0;
		SND_Channel[i].volume = 0;
		SND_Channel[i].sample = NULL;
		SND_Channel[i].subvolume = 0;
		SND_Channel[i].pan = 0;
		SND_Channel[i].locked = false;
	}

	for(i=0;i<MAX_STATICFX;i++)
	{
		SND_StaticChannels[i].attn = 0;
		SND_StaticChannels[i].chan = 0;
		SND_StaticChannels[i].channel = 0;
		SND_StaticChannels[i].ent = 0;
		SND_StaticChannels[i].inuse = false;
		SND_StaticChannels[i].origin[0] = 0;
		SND_StaticChannels[i].origin[1] = 0;
		SND_StaticChannels[i].origin[2] = 0;
		SND_StaticChannels[i].volume = 0;
		SND_StaticChannels[i].sample = NULL;
		SND_StaticChannels[i].subvolume = 0;
		SND_StaticChannels[i].pan = 0;
		SND_StaticChannels[i].locked = false;
	}

	Con_Printf("\nInitialized %i software channels, %i hardware channels\n", SND_SoftwareChannels, SND_HardwareChannels);

	fval = FSOUND_GetOutput();

	strcpy(OutputAPI[0], "Null Audio Driver");
	strcpy(OutputAPI[1], "Windows Wave Audio");
	strcpy(OutputAPI[2], "Microsoft DirectSound");
	strcpy(OutputAPI[3], "Aureal A3D");

	Con_Printf("using %s at %.0fhz\n", OutputAPI[fval], SND_Samplerate.value);

	SND_Initialised = 1;

	//Precache BSP Ambient Sound Effects, Force Loop Status & Lock channels
	SND_AmbientSamples[0] = S_PrecacheSound ("ambience/water1.wav");

	if(SND_AmbientSamples[0])
	{
		SND_AmbientSamples[0]->loops = true;
		FSOUND_Sample_SetLoopMode(SND_AmbientSamples[0]->sample, FSOUND_LOOP_NORMAL);
		SND_AmbientSfx[0] = &SND_Channel[1];
		SND_AmbientSfx[0]->locked = true;
		SND_AmbientSfx[0]->sample = SND_AmbientSamples[0];
	}

	SND_AmbientSamples[1] = S_PrecacheSound ("ambience/wind2.wav");

	if(SND_AmbientSamples[1])
	{
		SND_AmbientSamples[1]->loops = true;
		FSOUND_Sample_SetLoopMode(SND_AmbientSamples[1]->sample, FSOUND_LOOP_NORMAL);
		SND_AmbientSfx[1] = &SND_Channel[2];
		SND_AmbientSfx[1]->locked = true;
		SND_AmbientSfx[1]->sample = SND_AmbientSamples[1];
	}

	//Lock Music channel
	SND_MusicChannel = &SND_Channel[0];
	SND_MusicChannel->locked = true;

	return;
}

void S_Shutdown (void)
{
	FSOUND_Close();

	SND_Initialised = 0;
}


/*////////////////////////////////////////////////////////////////////////////////////
//                             Sound Sample Managment                               //
////////////////////////////////////////////////////////////////////////////////////*/

SND_Sample_t *S_PrecacheSound (const char *name)
{
	int i;
	unsigned int m;
	char file[MAX_QPATH];

	if(!SND_Initialised || SND_NoSound.value)
		return NULL;

	strcpy(file, "sound/");
	strcat(file, name);

	for(i=0;i<MAX_SFX;i++)
	{
		if(!strcmp(file, SND_Samples[i].name))
		{
			Con_DPrintf("Sample Already Precached '%s'\n", file);
			return &SND_Samples[i];
		}
	}

	if(SND_8bitOnly.value)
	{
		m = (FSOUND_LOOP_OFF | FSOUND_8BITS | FSOUND_MONO);
	}
	else
	{
		m = (FSOUND_LOOP_OFF | FSOUND_16BITS | FSOUND_MONO);
	}

	for(i=0;i<MAX_SFX;i++)
	{
		if(!SND_Samples[i].sample)
		{
			strcpy(SND_Samples[i].name, file);
			SND_Samples[i].sample = FSOUND_Sample_Load(FSOUND_FREE, SND_Samples[i].name, m, 0);

			if(SND_LoopPoints.loopstart != -1)
			{
				FSOUND_Sample_SetLoopPoints(SND_Samples[i].sample, SND_LoopPoints.loopstart, SND_LoopPoints.loopstart + SND_LoopPoints.looplength);
				FSOUND_Sample_SetLoopMode(SND_Samples[i].sample, FSOUND_LOOP_NORMAL);

				SND_Samples[i].loopstart = SND_LoopPoints.loopstart;
				SND_Samples[i].loopstop = SND_LoopPoints.loopstart + SND_LoopPoints.looplength;
				SND_Samples[i].loops = true;

				SND_LoopPoints.loopstart = -1;
				SND_LoopPoints.looplength = -1;
			}
			else
			{
				FSOUND_Sample_SetLoopMode(SND_Samples[i].sample, FSOUND_LOOP_OFF);

				SND_Samples[i].loopstart = -1;
				SND_Samples[i].loopstop = -1;
				SND_Samples[i].loops = false;

				SND_LoopPoints.loopstart = -1;
				SND_LoopPoints.looplength = -1;
			}

			if(!SND_Samples[i].sample)
			{
				return NULL;
			}

			return &SND_Samples[i];
		}
	}

	return NULL;
}

void SND_FreeSample (SND_Sample_t *SND_Sample)
{
	if(!SND_Initialised || SND_NoSound.value)
	{
		return;
	}

	strcpy(SND_Sample->name, "\0");
	FSOUND_Sample_Free(SND_Sample->sample);
	SND_Sample->sample = NULL;
}

void S_TouchSound (char *sample)
{
	SND_Sample_t *Touched;

	Touched = S_PrecacheSound(sample);
}

/*////////////////////////////////////////////////////////////////////////////////////
//                               3D Audio Managment                                 //
////////////////////////////////////////////////////////////////////////////////////*/

void S_UpdateAmbientSounds (void)
{
	mleaf_t			*l;
	float			vol;
	int				ambient_channel;

	if (SND_NoAmbient.value)
		return;

	// calc ambient sound levels
	if (!cl.worldmodel)
	{
		return;
	}

	l = Mod_PointInLeaf (SND_ListenerOrigin, cl.worldmodel);
	if (!l || !SND_AmbientLevel.value)
	{
		for (ambient_channel = 0 ; ambient_channel< 2 ; ambient_channel++)
		{
			if(SND_AmbientSfx[ambient_channel]->inuse)
			{
				SND_AmbientSfx[ambient_channel]->inuse = false;
				FSOUND_StopSound(SND_AmbientSfx[ambient_channel]->channel);
			}
		}

		return;
	}

	for (ambient_channel = 0 ; ambient_channel< 2 ; ambient_channel++)
	{
		if(!SND_AmbientSfx[ambient_channel])
			continue;

		vol = SND_AmbientLevel.value * l->ambient_sound_level[ambient_channel];
		if (vol < 8)
			vol = 0;

		if(!SND_AmbientSfx[ambient_channel]->inuse)
		{
			SND_AmbientSfx[ambient_channel]->sample = SND_AmbientSamples[ambient_channel];
			SND_AmbientSfx[ambient_channel]->inuse = true;
			SND_AmbientSfx[ambient_channel]->channel = FSOUND_PlaySound3DAttrib(SND_AmbientSfx[ambient_channel]->channel, SND_AmbientSfx[ambient_channel]->sample->sample, -1, 1, FSOUND_STEREOPAN, NULL, NULL);
		}

	// don't adjust volume too fast
		if (SND_AmbientSfx[ambient_channel]->volume < vol)
		{
			SND_AmbientSfx[ambient_channel]->volume += host_frametime * SND_AmbientFade.value;
			if (SND_AmbientSfx[ambient_channel]->volume > vol)
				SND_AmbientSfx[ambient_channel]->volume = vol;
		}
		else if (SND_AmbientSfx[ambient_channel]->volume > vol)
		{
			SND_AmbientSfx[ambient_channel]->volume -= host_frametime * SND_AmbientFade.value;
			if (SND_AmbientSfx[ambient_channel]->volume < vol)
				SND_AmbientSfx[ambient_channel]->volume = vol;
		}
		
		SND_AmbientSfx[ambient_channel]->subvolume = ((int)(SND_AmbientSfx[ambient_channel]->volume * SND_Volume.value));

		FSOUND_SetVolume(SND_AmbientSfx[ambient_channel]->channel, SND_AmbientSfx[ambient_channel]->subvolume);
	}
}

void S_UpdateStaticSounds (void)
{
	int i, j;

	if(!cl.worldmodel)
		return;

	for(i=0;i<MAX_STATICFX;i++)
	{
		if(SND_StaticChannels[i].inuse)
		{
			S_StaticSpat(&SND_StaticChannels[i]);

			if(!SND_StaticChannels[i].subvolume)
				continue;

			for(j=0;j<SND_TopUsedChannel;j++)
			{
				if(SND_Channel[j].sample != SND_StaticChannels[i].sample)
					continue;

				if(SND_Channel[j].subvolume)
				{
					SND_Channel[j].subvolume += SND_StaticChannels[i].subvolume;
				}
				else
				{
					SND_Channel[j].subvolume = SND_StaticChannels[i].subvolume;
				}

				break;
			}
		}
	}

	for(i=0;i<SND_TopUsedChannel;i++)
	{
		if(!SND_Channel[i].is_static)
			continue;

		if(SND_Channel[i].subvolume > 255)
			SND_Channel[i].subvolume = 255;

		FSOUND_SetVolume(SND_Channel[i].channel, ((int)(SND_Channel[i].subvolume * SND_Volume.value)));

		SND_Channel[i].subvolume = 0;
	}

}

void S_Update (vec3_t origin, vec3_t v_forward, vec3_t v_right, vec3_t v_up)
{
	int i, j;
	mleaf_t *leaf;

	if(!SND_Initialised || SND_NoSound.value || SND_IsBlocked)
		return;

	//Get top used channel
	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].inuse)
			SND_TopUsedChannel = i + 1;
	}

	if(SND_Volume.value != SND_ActualVolume)
	{
		SND_ActualVolume = SND_Volume.value;

		for(i=0;i<SND_TopUsedChannel;i++)
		{
			if(!SND_Channel[i].inuse || SND_Channel[i].locked)
				continue;

			FSOUND_SetVolume(SND_Channel[i].channel, ((int)SND_Channel[i].subvolume * SND_Volume.value));
		}
	}

	if(SND_MusicVolume.value != SND_ActualMusicVolume)
	{
		SND_ActualMusicVolume = SND_MusicVolume.value;

		if(SND_MusicChannel->inuse)
		{
			if(!SND_MusicVolume.value && !SND_MusicPaused)
			{
				S_PauseMusic();
			}

			if(SND_MusicVolume.value && SND_MusicPaused)
			{
				S_ResumeMusic();
			}

			FSOUND_SetVolume(SND_MusicChannel->channel, ((int)(SND_MusicVolume.value * 255)));
		}
	}

	if(SND_FakeDolby.value != SND_FakeDolbyEnabled)
	{
		SND_FakeDolbyEnabled = SND_FakeDolby.value;

		if(SND_FakeDolbyEnabled)
		{
			FSOUND_SetSurround(FSOUND_ALL, true);
		}
		else
		{
			FSOUND_SetSurround(FSOUND_ALL, false);
		}
	}

	if(SND_NoWaterEffect.value != SND_WaterEffectDisabled)
	{
		SND_WaterEffectDisabled = SND_NoWaterEffect.value;
	}

	if(!SND_WaterEffectDisabled && cl.worldmodel)
	{
		leaf = Mod_PointInLeaf (origin, cl.worldmodel);

		if(SND_LastContent != leaf->contents)
		{
			SND_LastContent = leaf->contents;

			if(leaf->contents <= CONTENTS_WATER)
			{
				for(i=0;i<SND_TopUsedChannel;i++)
				{
					if(SND_Channel[i].channel == SND_MusicChannel->channel)
						continue;

					FSOUND_SetFrequency(SND_Channel[i].channel, ((int)(SND_Channel[i].samplerate * SND_WaterPitch.value)));
				}
			}
			else
			{
				for(i=0;i<SND_TopUsedChannel;i++)
				{
					if(SND_Channel[i].channel == SND_MusicChannel->channel)
						continue;

					FSOUND_SetFrequency(SND_Channel[i].channel, SND_Channel[i].samplerate);
				}
			}
		}
	}

	for(i=0;i<SND_TopUsedChannel;i++)
	{
		if(!FSOUND_IsPlaying(SND_Channel[i].channel) && SND_Channel[i].inuse && !SND_Channel[i].locked)
		{
			FSOUND_StopSound(SND_Channel[i].channel);
			
			SND_Channel[i].ent = -1;
			SND_Channel[i].chan = -1;
			SND_Channel[i].inuse = false;
			SND_Channel[i].sample = NULL;
		}
	}

	VectorCopy(origin	, SND_ListenerOrigin);
	VectorCopy(v_forward, SND_ListenerForward);
	VectorCopy(v_up		, SND_ListenerUp);
	VectorCopy(v_right	, SND_ListenerRight);

	S_UpdateAmbientSounds();

	S_UpdateStaticSounds();

	//Re-Spatialize All Channels, except locked channels
	for(i=0;i<SND_TopUsedChannel;i++)
	{
		if(SND_Channel[i].locked || SND_Channel[i].is_static)
			continue;

		if(SND_Channel[i].inuse)
		{
			S_Spatialize(&SND_Channel[i]);
			FSOUND_SetPan(SND_Channel[i].channel, SND_Channel[i].pan);

			j = (int) (SND_Channel[i].subvolume * SND_Volume.value);

			FSOUND_SetVolume(SND_Channel[i].channel, j);
		}
	}
			
	FSOUND_3D_Update();

	if(SND_ChanStats.value)
	{
		char debug[128] = "";

		for(i=0;i<SND_ChanStats.value;i++)
		{
			if(SND_Channel[i].inuse)
			{
				debug[i] = 'D' + (char)128;

				if(SND_MusicChannel->channel == SND_Channel[i].channel)
					debug[i] = 'M' + (char)128;

				if((SND_AmbientSfx[0]->channel == SND_Channel[i].channel) || (SND_AmbientSfx[1]->channel == SND_Channel[i].channel))
					debug[i] = 'A' + (char)128;

				if(SND_Channel[i].is_static)
					debug[i] = 'S' + (char)128;
			}
			else
			{
				if(!SND_Channel[i].locked)
					debug[i] = '-';
				else
					debug[i] = '=';
			}
		}

		Con_Printf("|%s|\n", debug);
	}
}

void S_ExtraUpdate (void)
{
	if(!SND_Initialised || SND_NoSound.value || SND_IsBlocked)
		return;

	FSOUND_3D_Update();
}

void S_BlockSound (void)
{
	int i;

	SND_IsBlocked = 1;

	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].inuse)
		{
			if(SND_MusicChannel)
			{
				if(SND_MusicChannel->channel == SND_Channel[i].channel)
					continue;
			}

			FSOUND_SetPaused(SND_Channel[i].channel, true);
		}
	}

	if(SND_MusicStream)
	{
		S_PauseMusic();
	}
}

void S_UnblockSound (void)
{
	int i;

	SND_IsBlocked = 0;

	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].inuse)
		{
			if(SND_MusicChannel)
			{
				if(SND_MusicChannel->channel == SND_Channel[i].channel)
					continue;
			}

			FSOUND_SetPaused(SND_Channel[i].channel, false);
		}
	}

	if(SND_MusicStream)
	{
		S_ResumeMusic();
	}
}


SND_Channels_t *S_AllocateChannel(int entnum, int entchannel, int locked)
{
	int i;
	int used[7];

	//Build Used Chan List for Entity
	if(entnum >= 0)
	{
		for(i=0;i<MAX_CHANNELS;i++)
		{
			if(SND_Channel[i].ent == entnum && SND_Channel[i].chan == entchannel)
			{
				SND_Channel[i].ent = entnum;
				SND_Channel[i].chan = entchannel;
				SND_Channel[i].channel = i;
				SND_Channel[i].inuse = true;

				if(locked)
				{
					SND_Channel[i].locked = true;
				}
				else
				{
					SND_Channel[i].locked = false;
				}

				FSOUND_StopSound(SND_Channel[i].channel);

				Con_DPrintf("Overwritten Channel %i from Entity %i\n", entnum, entchannel);
				return &SND_Channel[i];
			}
		}

		if (entchannel == 0) //New code - DigitalBlur
		{
			for(i=0;i<7;i++)
			{
				used[i] = 0;
			}

			for(i=0;i<7;i++)
			{
				if(SND_Channel[i].ent == entnum)
				{	
					used[SND_Channel[i].chan - 1] = 1;
				}
			}

			for(i=0;i<7;i++)
			{
				if(!used[i])
				{
					entchannel = i + 1;
					break;
				}
			}

			if(!entchannel)
			return NULL;
		}
	}

	// Must be a local sound
	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].locked)
			continue;

		if(!SND_Channel[i].inuse && !SND_Channel[i].locked)
		{
			SND_Channel[i].ent = entnum;
			SND_Channel[i].chan = entchannel;
			SND_Channel[i].channel = i;
			SND_Channel[i].inuse = true;

			if(locked)
			{
				SND_Channel[i].locked = true;
			}
			else
			{
				SND_Channel[i].locked = false;
			}


			return &SND_Channel[i];
		}
	}

	return NULL;
}

void S_Spatialize(SND_Channels_t *ch)
{
    vec_t dot;
    vec_t dist;
    vec_t oscale, scale;
    vec3_t source_vec;

// anything coming from the view entity will allways be full volume
	if (ch->ent == cl.viewentity)
	{
		ch->pan = FSOUND_STEREOPAN;
		ch->subvolume = ch->volume;
		return;
	}

// calculate stereo seperation and distance attenuation

	VectorSubtract(ch->origin, SND_ListenerOrigin, source_vec);
	
	dist = VectorNormalize(source_vec) * ch->attn;
	
	dot = DotProduct(SND_ListenerForward, source_vec); // Tomaz Bug Report Fix

	oscale = 1.0 + dot;

	ch->pan = ((int)128 * oscale);

// add in distance effect
	scale = (1.0 - dist);

	ch->subvolume = (int) (ch->volume * scale);

	if (ch->subvolume < 0)
		ch->subvolume = 0;
}

void S_StaticSpat(SND_Channels_t *ch)
{
    vec_t dist;
    vec_t scale;
    vec3_t source_vec;

// calculate distance attenuation
	VectorSubtract(ch->origin, SND_ListenerOrigin, source_vec);
	
	dist = VectorNormalize(source_vec) * ch->attn;
	
// add in distance effect
	scale = (1.0 - dist);
	ch->subvolume = (int) (ch->volume * scale);

	if (ch->subvolume < 0)
		ch->subvolume = 0;
} 

void S_StartSound (int entnum, int entchannel, SND_Sample_t *sfx, vec3_t origin, float fvol,  float attenuation)
{
	SND_Channels_t	*Channel;
	int				rvol;
	int				rate;

	if(!SND_Initialised || SND_NoSound.value || !sfx)
		return;

	Channel = S_AllocateChannel(entnum, entchannel, 0);
	if(!Channel)
	{
		return;
	}

	Channel->attn = attenuation * 0.001;
	Channel->volume = ((int)fvol * 255);
	Channel->inuse = true;
	Channel->sample = sfx;
	VectorCopy(origin, Channel->origin);

	S_Spatialize(Channel);

	rvol = (int) (Channel->subvolume * SND_Volume.value);

	if(rvol <= SND_QuietLevel.value)
	{
		Channel->inuse = false;
		return;
	}

	FSOUND_Sample_GetDefaults(Channel->sample->sample, &Channel->samplerate, NULL, NULL, NULL);

	if(SND_LastContent <= CONTENTS_WATER && !SND_NoWaterEffect.value && entchannel != -2)
	{
		rate = Channel->samplerate * SND_WaterPitch.value;
	}
	else
	{
	//	if (!SND_NoSlowmoEffect.value)  tribal take all this away
	//		rate = Channel->samplerate * slowmo.value;
	//	else
			rate = Channel->samplerate;
	}

	Channel->channel = FSOUND_PlaySound3DAttrib(Channel->channel, sfx->sample, rate, rvol, Channel->pan, NULL, NULL);	
}

void S_StaticSound (SND_Sample_t *sfx, vec3_t origin, float vol, float attenuation)
{
	int	i, j;
	SND_Channels_t *newchan;

	if(!SND_Initialised || SND_NoSound.value || !sfx)
		return;

	j = 0;
	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].sample == sfx)
		{
			j = 1;
			break;
		}
	}

	if(!j)
	{
		newchan = S_AllocateChannel(-1, -1, true);

		if(!newchan)
			return;

		newchan->inuse = true;
		newchan->sample = sfx;
		newchan->is_static = true;
		newchan->volume = 0;
		newchan->pan = FSOUND_STEREOPAN;
		newchan->channel = FSOUND_PlaySound3DAttrib(newchan->channel, newchan->sample->sample, -1, newchan->volume, newchan->pan, NULL, NULL);
	}

	for(i=0;i<MAX_STATICFX;i++)
	{
		if(!SND_StaticChannels[i].inuse)
		{
			SND_StaticChannels[i].ent = -1;
			SND_StaticChannels[i].chan = -1;
			SND_StaticChannels[i].attn = (attenuation * 0.015625) * 0.001;	// Tomaz - Speed
			SND_StaticChannels[i].volume = vol;
			SND_StaticChannels[i].inuse = true;
			SND_StaticChannels[i].sample = sfx;
			SND_StaticChannels[i].is_static = true;
			SND_StaticChannels[i].looping = true;
			VectorCopy(origin, SND_StaticChannels[i].origin);

			FSOUND_Sample_SetLoopMode(SND_StaticChannels[i].sample->sample, FSOUND_LOOP_NORMAL);
			SND_StaticChannels[i].sample->loops = true;

			return;
		}
	}
}


void S_LocalSound (char *name)
{
	SND_Sample_t	*sfx;

	if(!SND_Initialised || SND_NoSound.value)
		return;

	sfx = S_PrecacheSound (name);
	if (!sfx)
	{
		Con_Printf ("S_LocalSound: can't cache %s\n", name);
		return;
	}
	S_StartSound (cl.viewentity, -2, sfx, vec3_origin, 1, 1);
}

void S_Play(void)
{
	char			name[MAX_QPATH];
	char			fname[MAX_QPATH];
	SND_Sample_t	*sfx;

	COM_StripExtension(Cmd_Argv (1), name);

	sprintf(fname, "%s.wav", name);
 	sfx = S_PrecacheSound(fname);

	if(!sfx)
	{
		sprintf(fname, "%s.mp3", name);
 		sfx = S_PrecacheSound(fname);
	}

 	S_StartSound (cl.viewentity, -2, sfx, vec3_origin, 1, 1);
}

void S_PlayVol(void)
{
	int 			i;
	float			vol;
	char			name[MAX_QPATH];
	char			fname[MAX_QPATH];
	SND_Sample_t	*sfx;

	i = 1;
	while (i<Cmd_Argc())
	{
		if (!Q_strrchr(Cmd_Argv(i), '.'))
 		{
 			Q_strcpy(name, Cmd_Argv(i));
			break;
 		}
 		else
		{
 			Q_strcpy(name, Cmd_Argv(i));
		}

		i++;
	}

	sprintf(fname, "%s.wav", name);
 	sfx = S_PrecacheSound(fname);

	if(!sfx)
	{
		sprintf(fname, "%s.mp3", name);
 		sfx = S_PrecacheSound(fname);
	}

	vol = Q_atof(Cmd_Argv(i+1));

 	S_StartSound (cl.viewentity, -2, sfx, vec3_origin, vol, 1);
}

void S_StopSound (int entnum, int entchannel)
{
	int i;

	if(!SND_Initialised || SND_NoSound.value)
		return;

	for(i=0;i<MAX_CHANNELS;i++)
	{
		if((SND_Channel[i].ent == entnum) && (SND_Channel[i].chan == entchannel))
		{
			FSOUND_StopSound(SND_Channel[i].channel);
			
			SND_Channel[i].attn = 0;
			SND_Channel[i].chan = 0;
			SND_Channel[i].ent = -2;
			SND_Channel[i].inuse = true;
			SND_Channel[i].origin[0] = 0;
			SND_Channel[i].origin[1] = 0;
			SND_Channel[i].origin[2] = 0;
			SND_Channel[i].volume = 0;
			SND_Channel[i].sample = NULL;
			SND_Channel[i].subvolume = 0;
			SND_Channel[i].pan = 0;

			return;
		}
	}
}

void S_StopAllSounds (qboolean clear)
{
	int i;

	if(!SND_Initialised || SND_NoSound.value)
		return;

	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].inuse)
		{
			if(SND_MusicChannel->channel == SND_Channel[i].channel)
				continue;

			FSOUND_StopSound(SND_Channel[i].channel);
			
			SND_Channel[i].attn = 0;
			SND_Channel[i].chan = 0;
			SND_Channel[i].ent = -2;
			SND_Channel[i].inuse = false;
			SND_Channel[i].origin[0] = 0;
			SND_Channel[i].origin[1] = 0;
			SND_Channel[i].origin[2] = 0;
			SND_Channel[i].volume = 0;
			SND_Channel[i].sample = NULL;
			SND_Channel[i].subvolume = 0;
			SND_Channel[i].pan = 0;

			//Unlock Static Channels, as not all may be needed next map, so don't waste channels
			if(SND_Channel[i].is_static && SND_Channel[i].locked)
			{
				SND_Channel[i].is_static = false;
				SND_Channel[i].locked = false;
			}
		}
	}

	for(i=0;i<MAX_STATICFX;i++)
	{
		if(SND_StaticChannels[i].inuse)
		{
			SND_StaticChannels[i].inuse = false;
			SND_StaticChannels[i].attn = 0;
			SND_StaticChannels[i].volume = 0;
			SND_StaticChannels[i].sample = NULL;
			SND_StaticChannels[i].is_static = false;
			SND_StaticChannels[i].looping = 0;
			SND_StaticChannels[i].origin[0] = 0;
			SND_StaticChannels[i].origin[1] = 0;
			SND_StaticChannels[i].origin[2] = 0;
		}
	}

	S_ExtraUpdate();
}

/*////////////////////////////////////////////////////////////////////////////////////
//                           Streaming Audio Managment                              //
////////////////////////////////////////////////////////////////////////////////////*/

void S_StartMusic (char *name, int loop)
{
	if(!SND_Initialised || SND_NoSound.value)
		return;

	if(SND_MusicChannel->inuse)
	{
		S_StopMusic();
	}

	if(loop)
	{
		SND_MusicStream = FSOUND_Stream_OpenFile(name, (FSOUND_LOOP_NORMAL | FSOUND_2D), 0);
		SND_MusicLooping = 1;
	}
	else
	{
		SND_MusicStream = FSOUND_Stream_OpenFile(name, FSOUND_2D, 0);
		SND_MusicLooping = 0;
	}

	if(!SND_MusicStream)
	{
		// disabled those ugly msg'es - nothing to fix, since nothing is broken
	//	Con_Printf("Couldn't open stream %s\n", name);
		return;
	}

	strcpy(SND_FakeMusicSample.name, name);
	SND_FakeMusicSample.sample = NULL;
	SND_MusicChannel->inuse = true;
	SND_MusicChannel->sample = &SND_FakeMusicSample;

	SND_MusicChannel->channel = FSOUND_Stream_Play3DAttrib(SND_MusicChannel->channel, SND_MusicStream, -1, ((int)(SND_MusicVolume.value * 255)), FSOUND_STEREOPAN, NULL, NULL);

	if(SND_FakeDolby.value)
	{
		FSOUND_SetSurround(SND_MusicChannel->channel, true);
	}
	else
	{
		FSOUND_SetSurround(SND_MusicChannel->channel, false);
	}
}

void S_StopMusic (void)
{
	if(!SND_Initialised || SND_NoSound.value || !SND_MusicStream)
		return;

	FSOUND_Stream_Stop(SND_MusicStream);
	FSOUND_Stream_Close(SND_MusicStream);

	SND_MusicStream = NULL;

	//Clear Fake Channel
	SND_MusicChannel->inuse = false;
	SND_MusicChannel->sample = NULL;
}

void S_PauseMusic (void)
{
	if(!SND_Initialised || SND_NoSound.value || !SND_MusicChannel->inuse)
		return;

	FSOUND_Stream_SetPaused(SND_MusicStream, true);

	SND_MusicPaused = 1;
}

void S_ResumeMusic (void)
{
	if(!SND_Initialised || SND_NoSound.value || !SND_MusicChannel->inuse)
		return;

	FSOUND_Stream_SetPaused(SND_MusicStream, false);

	SND_MusicPaused = 0;
}

void S_StartMusicConsole (void)
{
	char file[MAX_QPATH];

	if (Cmd_Argc() < 2)
	{
		Con_Printf ("s_startmusic <trackname> : play an audio track\n");
		return;
	}

	sprintf(file, "%s\0", Cmd_Argv(1));
	S_StartMusic(file, false);
}

/*////////////////////////////////////////////////////////////////////////////////////
//                                      Debug                                       //
////////////////////////////////////////////////////////////////////////////////////*/

void S_ChannelReport (void)
{
	int i, fval;

	Con_Printf("== Sound System Report ==\n");

	fval = FSOUND_GetOutput();
	Con_Printf("Output API         : %s\n", OutputAPI[fval]);
	Con_Printf("Sample Rate        : %ihz\n", SND_Samplerate.value);
	Con_Printf("Software Channels  : %i\n", SND_SoftwareChannels);
	Con_Printf("Hardware Channels  : %i\n", SND_HardwareChannels);
	Con_Printf("Total Channels     : %i\n", SND_HardwareChannels + SND_SoftwareChannels);

	fval = 0;
	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(FSOUND_IsPlaying(i))
			fval++;
	}
	Con_Printf("Active Channels    : %i\n", fval);

	fval = 0;
	for(i=0;i<MAX_SFX;i++)
	{
		if(SND_Samples[i].sample)
			fval++;
	}
	Con_Printf("Total Samples      : %i\n", MAX_SFX);
	Con_Printf("Active Samples     : %i\n", fval);

	Con_Printf("== Active Channel Breakdown ==\n");
	for(i=0;i<MAX_CHANNELS;i++)
	{
		if(SND_Channel[i].inuse)
		{
			Con_Printf("Channel %i:\n Name = %s\n Ent = %i, Chan = %i, Attn = %f, InUse = %i\n", i, SND_Channel[i].sample->name, SND_Channel[i].ent, SND_Channel[i].chan, SND_Channel[i].attn, SND_Channel[i].inuse);

		}
	}
}

void S_SampleReport (void)
{
	int i;

	for(i=0;i<MAX_SFX;i++)
	{
		if(SND_Samples[i].sample)
		{
			Con_Printf("Sample #%i: \"%s\"\n", i, SND_Samples[i].name);
		}
	}
}

/*
  I must provide all these
*/
void S_AmbientOff (void)
{
}

void S_AmbientOn (void)
{
}

void S_ClearBuffer (void)
{
}

void S_ClearPrecache (void)
{
}

void S_BeginPrecaching (void)
{
}

void S_EndPrecaching (void)
{
}