//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/snd_dma.c                                              $
// $Revision:: 3                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/8/01 4:49 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// main control for any streaming sound output device
//

#include "client.h"
#include "snd_loc.h"

void S_PlayMusic( void );
void S_LoadMusic( void );

#ifdef __USEA3D
#include "sin_a3d.h"
#include "A3DWrapperDP.h"
#include "winquake.h"

#ifdef __A3D_GEOM
#include "sin_a3dg.h"
#endif

// DEBUGGING
#if _DEBUG
// This functioon sole purpose is for breaking dring execution
// whenever I want.
void a3d_debug( void )
{
	int i = 0;
	i = 1;
}
#endif

void S_enableA3D( void );
void S_disableA3D( void );

#endif // __USEA3D

#ifdef _ADD_EAX_
#include "eax.h"
void SEAX_SpatializeOrigin( vec3_t origin, float master_vol, float dist_mult, int *left_vol, int *right_vol, int *rleft_vol, int *rright_vol );
#endif

// =======================================================================
// Internal sound data & structures
// =======================================================================

// only begin attenuating sound volumes when outside the FULLVOLUME range
#define     SOUND_FULLVOLUME    80

#define     SOUND_LOOPATTENUATE 0.003

int s_registration_sequence;

channel_t channels[MAX_CHANNELS];

qboolean snd_initialized = false;
int sound_started=0;

dma_t dma;

vec3_t listener_origin;
vec3_t listener_forward;
vec3_t listener_right;
vec3_t listener_up;

qboolean s_registering;

int soundtime;              // sample PAIRS
int paintedtime;            // sample PAIRS

// during registration it is possible to have more sounds
// than could actually be referenced during gameplay,
// because we don't want to free anything until we are
// sure we won't need it.
#define     MAX_SFX     (MAX_SOUNDS*2)
sfx_t known_sfx[MAX_SFX];
int num_sfx;

#define     MAX_PLAYSOUNDS  128
playsound_t s_playsounds[MAX_PLAYSOUNDS];
playsound_t s_freeplays;
playsound_t s_pendingplays;

int s_beginofs;

cvar_t      *s_volume;
cvar_t      *s_testsound;
cvar_t      *s_sounddir;
cvar_t      *s_loadas8bit;
cvar_t      *s_khz;
cvar_t      *s_show;
cvar_t      *s_mixahead;
cvar_t      *s_primary;
cvar_t      *s_music;
cvar_t      *s_musicdir;
cvar_t      *s_musicvolume;
cvar_t      *s_dialogvolume;


#ifdef __USEA3D
qboolean snd_isa3d;

cvar_t *gpclRolloff;
cvar_t *gpclDoppler;
cvar_t *gpclDistance;
cvar_t *gpclMin;
cvar_t *gpclMax;
cvar_t *gpcl2DVolume;
cvar_t *gpclWaterEq;

// A3D 2.0 specific
cvar_t *gpclGeometry;
cvar_t *gpclNumLeaf;
cvar_t *gpclRefGain;
cvar_t *gpclRefDelay;
cvar_t *gpclPolyKeep;
cvar_t *gpclPolySize;
cvar_t *gpclShowTossed;
cvar_t *gpclPolyReflectSize;
cvar_t *gpclNumPolys;
cvar_t *gpclUsePVS;
cvar_t *gpclBloat;
cvar_t *gpclOcclude;
cvar_t *gpclReflect;
cvar_t *gpclOccFact;
cvar_t *gpclOccEpsilon;
#endif

int s_rawend;
portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
extern int havegus;  /* FS: Added */


/*
===============================================================================

console functions

===============================================================================
*/

static void S_Play( void )
{
	int i;
	char name[256];
	sfx_t   *sfx;

	i = 1;
	while (i<Cmd_Argc())
	{
		if (!strrchr( Cmd_Argv( i ), '.' ))
		{
			strcpy( name, Cmd_Argv( i ));
			strcat( name, ".wav" );
		}
		else
			strcpy( name, Cmd_Argv( i ));
		sfx = S_RegisterSound( name );
		S_StartSound( NULL, cl.playernum+1, 0, sfx, 1.0, 1.0, 0 );
		i++;
	}
}

static void S_SoundList( void )
{
	int i;
	sfx_t   *sfx;
	sfxcache_t  *sc;
	int size, total;

	total = 0;
	for (sfx=known_sfx, i=0; i<num_sfx; i++, sfx++)
	{
		sc = sfx->cache;
		if (!sc)
			continue;
		size = sc->real_length;
		total += size;
		if (sc->loopstart >= 0)
			Com_Printf ( "L" );
		else
			Com_Printf ( " " );
		if (sc->stereo)
			Com_Printf ( "S" );
		else
			Com_Printf ( " " );
		Com_Printf( "(%2db) %6i : %s\n",sc->width*8,  size, sfx->name );
	}
	Com_Printf ( "Total resident: %i\n", total );
}
// ====================================================================
// User-setable variables
// ====================================================================


static void S_SoundInfo_f( void )
{
	if (!sound_started)
	{
		Com_Printf ( "sound system not started\n" );
		return;
	}

	Com_Printf( "sounddir: %s\n", s_sounddir->string );
	Com_Printf( "%5d stereo\n", dma.channels - 1 );
	Com_Printf( "%5d samples\n", dma.samples );
	Com_Printf( "%5d samplepos\n", dma.samplepos );
	Com_Printf( "%5d samplebits\n", dma.samplebits );
	Com_Printf( "%5d submission_chunk\n", dma.submission_chunk );
	Com_Printf( "%5d speed\n", dma.speed );
	Com_Printf( "%p dma buffer\n", dma.buffer );
}



/*
================
S_Init
================
*/
void S_Init( void )
{
	cvar_t  *cv;

	Com_Printf( "\n------- sound initialization -------\n" );

	cv = Cvar_Get ( "s_initsound", "1", 0 );
	if (!cv->value)
		Com_Printf ( "not initializing.\n" );
	else
	{
		s_volume = Cvar_Get ( "s_volume", "0.7", CVAR_ARCHIVE );
		s_sounddir = Cvar_Get ( "s_sounddir", "sound", CVAR_ARCHIVE );
		s_khz = Cvar_Get ( "s_khz", "22", CVAR_ARCHIVE );
		s_loadas8bit = Cvar_Get ( "s_loadas8bit", "0", CVAR_ARCHIVE );
		s_mixahead = Cvar_Get ( "s_mixahead", "0.2", CVAR_ARCHIVE );
		s_show = Cvar_Get ( "s_show", "0", 0 );
		s_testsound = Cvar_Get ( "s_testsound", "0", 0 );
		s_primary = Cvar_Get ( "s_primary", "0", CVAR_ARCHIVE );
		s_musicvolume = Cvar_Get ( "s_musicvolume", "0.9", CVAR_ARCHIVE );
		s_dialogvolume = Cvar_Get ( "s_dialogvolume", "1", CVAR_ARCHIVE );
		s_musicdir = Cvar_Get ( "s_musicdir", "music", CVAR_ARCHIVE );
		s_music = Cvar_Get ( "s_music", "1", CVAR_ARCHIVE );

		Cmd_AddCommand( "music", S_PlayMusic );
		Cmd_AddCommand( "soundtrack", S_LoadMusic );

#ifdef __USEA3D
		// DEBUGGING
#if _DEBUG
		// Disable archinving of vars.  This allows me to update other
		// peoples console vars with a new build.
		#define A3D_ARCHIVE     0

		Cmd_AddCommand( "a3d_debug", a3d_debug );
#else
		#define A3D_ARCHIVE     CVAR_ARCHIVE
#endif

		Cmd_AddCommand( "enable_a3d", S_enableA3D );
		Cmd_AddCommand( "disable_a3d", S_disableA3D );

		gpclRolloff         = Cvar_Get ( "s_rolloff", "0.9", A3D_ARCHIVE );
		gpclDoppler         = Cvar_Get ( "s_doppler", "0.0", A3D_ARCHIVE );
		gpclDistance        = Cvar_Get ( "s_distance", "23", A3D_ARCHIVE );
		gpclMin             = Cvar_Get ( "s_min_distance", "11", A3D_ARCHIVE );
		gpclMax             = Cvar_Get ( "s_max_distance", "10000", A3D_ARCHIVE );

		gpcl2DVolume        = Cvar_Get ( "s_2dvolume", "0.88", A3D_ARCHIVE );
		gpclWaterEq         = Cvar_Get ( "s_watereq", "0.1", A3D_ARCHIVE );

#ifndef __A3D_GEOM
		gpclGeometry        = Cvar_Get ( "s_geometry", "0", 0 );
#else
		gpclGeometry        = Cvar_Get ( "s_geometry", "1", 0 );
		gpclNumLeaf         = Cvar_Get ( "s_leafnum", "0", A3D_ARCHIVE );
		gpclRefGain         = Cvar_Get ( "s_refgain", "0.38", A3D_ARCHIVE );
		gpclRefDelay        = Cvar_Get ( "s_refdelay", "1.2", A3D_ARCHIVE );
		gpclPolyKeep        = Cvar_Get ( "s_polykeep", "10000000", A3D_ARCHIVE );
		gpclPolySize        = Cvar_Get ( "s_polysize", "100000", A3D_ARCHIVE );
		gpclShowTossed      = Cvar_Get ( "s_showtossed", "0", 0 );
		gpclPolyReflectSize = Cvar_Get ( "s_polyreflectsize", "1000000", A3D_ARCHIVE );
		gpclNumPolys        = Cvar_Get ( "s_numpolys", "400", A3D_ARCHIVE );
		gpclUsePVS          = Cvar_Get ( "s_usepvs", "1", 0 );
		gpclBloat           = Cvar_Get ( "s_bloat", "1.0001", A3D_ARCHIVE );
		gpclOcclude         = Cvar_Get ( "s_occlude", "1", 0 );
		gpclReflect         = Cvar_Get ( "s_reflect", "1", 0 );
		gpclOccFact         = Cvar_Get ( "s_occfactor", "0.4", A3D_ARCHIVE );
		gpclOccEpsilon      = Cvar_Get ( "s_occ_epsilon", "1.0", A3D_ARCHIVE );
#endif // __A3D_GEOM
#endif // __USEA3D
		Cmd_AddCommand( "play", S_Play );
		Cmd_AddCommand( "stopsound", S_StopAllSounds );
		Cmd_AddCommand( "soundlist", S_SoundList );
		Cmd_AddCommand( "soundinfo", S_SoundInfo_f );

		if (s_musicvolume->value > 1)
		{
			Com_DPrintf(DEVELOPER_MSG_SOUND, "Clamping music volume\n");
			s_musicvolume->value = 0.7f;
		}

		if (!SNDDMA_Init()) {
			dma.buffer = NULL;/* just in case */
			return;
		}

		S_InitScaletable ();

		sound_started = 1;
		num_sfx = 0;

		soundtime = 0;
		paintedtime = 0;

		Com_Printf ( "sound sampling rate: %i\n", dma.speed );

		S_StopAllSounds ();
	}

#if 0
	if (snd_isa3d)
	{
		A3D_SetGlobals( gpclDoppler->value, gpclDistance->value, gpclRolloff->value );
		hA3D_SetMinMax ( gpclMin->value, gpclMax->value );
		A3D_SetMixBufferVolume ( gpcl2DVolume->value );
	}
#endif

	Com_Printf( "------------------------------------\n" );
}


// =======================================================================
// Shutdown sound engine
// =======================================================================

void S_Shutdown( void )
{
	int i;
	sfx_t   *sfx;

	if (!sound_started)
		return;

	SNDDMA_Shutdown();

	dma.buffer = NULL;
	sound_started = 0;

	Cmd_RemoveCommand( "music" );
	Cmd_RemoveCommand( "soundtrack" );
	MUSIC_Shutdown();

#ifdef __USEA3D
	Cmd_RemoveCommand( "enable_a3d" );
	Cmd_RemoveCommand( "disable_a3d" );

#if _DEBUG
	Cmd_RemoveCommand( "a3d_debug" );
#endif
#endif

	Cmd_RemoveCommand( "play" );
	Cmd_RemoveCommand( "stopsound" );
	Cmd_RemoveCommand( "soundlist" );
	Cmd_RemoveCommand( "soundinfo" );

	// free all sounds
	for (i=0, sfx=known_sfx; i < num_sfx; i++,sfx++)
	{
		if (!sfx->name[0])
			continue;
		if (sfx->cache)
			S_FreeSound( sfx );
		memset ( sfx, 0, sizeof(*sfx));
	}

	num_sfx = 0;
}


// =======================================================================
// Load a sound
// =======================================================================

/*
==================
S_FindName

==================
*/
static sfx_t *S_FindName( const char *name )
{
	int i;
	sfx_t   *sfx;

	if (!name)
		Com_Error ( ERR_FATAL, "S_FindName: NULL\n" );
	if (!name[0])
		Com_Error ( ERR_FATAL, "S_FindName: empty name\n" );

	if (strlen( name ) >= MAX_QPATH)
		Com_Error ( ERR_FATAL, "Sound name too long: %s", name );

	// see if already loaded
	for (i=0; i < num_sfx; i++)
		if (!Q_stricmp( known_sfx[i].name, name ))
		{
			return &known_sfx[i];
		}

	// find a free sfx
	for (i=0; i < num_sfx; i++)
		if (!known_sfx[i].name[0])
//			registration_sequence < s_registration_sequence)
			break;

	if (i == num_sfx)
	{
		if (num_sfx == MAX_SFX)
			Com_Error ( ERR_FATAL, "S_FindName: out of sfx_t" );
		num_sfx++;
	}

	sfx = &known_sfx[i];
	memset ( sfx, 0, sizeof(*sfx));
	Q_strncpyz ( sfx->name, name, sizeof(sfx->name));
	sfx->registration_sequence = s_registration_sequence;

	return sfx;
}


/*
=====================
S_BeginRegistration

=====================
*/
void S_BeginRegistration( void )
{
	// initialize the dynamic speech sounds
	CL_InitTalkSounds();
	s_registration_sequence++;
	s_registering = true;
}

/*
==================
S_RegisterSound

==================
*/
sfx_t *S_RegisterSound( const char *name )
{
	sfx_t   *sfx;

	if (!sound_started)
		return NULL;

	sfx = S_FindName ( name );
	sfx->registration_sequence = s_registration_sequence;

	if (!s_registering)
		S_LoadSound ( sfx );

	return sfx;
}


/*
=====================
S_EndRegistration

=====================
*/
void S_EndRegistration( qboolean status )
{
	int i;
	sfx_t   *sfx;
	int size;

	// register music
	MUSIC_RegisterSongs();

	// free any sounds not from this registration sequence
	for (i=0, sfx=known_sfx; i < num_sfx; i++,sfx++)
	{
		if (!sfx->name[0])
			continue;
		if (sfx->registration_sequence != s_registration_sequence)
		{   // don't need this sound
			if (sfx->cache) // it is possible to have a leftover
				S_FreeSound( sfx );
			memset ( sfx, 0, sizeof(*sfx));
		}
		else
		{   // make sure it is paged in
			if (sfx->cache)
			{
				size = sfx->cache->length*sfx->cache->width*(sfx->cache->stereo+1);
				Com_PageInMemory ((byte *)sfx->cache, size );
			}
		}

	}

	// load everything in
	for (i=0, sfx=known_sfx; i < num_sfx; i++,sfx++)
	{
		if ( status )
		{
			if ( !( i % 20 ) )
				Com_UpdateStatus( cl_current++, cl_total, "Loading Sounds" );
			else
				cl_current++;
		}
		if (!sfx->name[0])
			continue;
		S_LoadSound ( sfx );
	}

	if ( status )
	{
		cl_current += ( MAX_SOUNDS - i );
		Com_UpdateStatus( cl_current, cl_total, "Loading Sounds" );
	}

	s_registering = false;
}


//=============================================================================

/*
=================
S_PickChannel
=================
*/
channel_t *S_PickChannel( int entnum, int entchannel
#ifdef __USEA3D
                          , qboolean bUseAutoSettings, sfxcache_t *sc, sfx_t *sfx
#endif
                          )
{
	int ch_idx;
	int first_to_die;
	int life_left;
	channel_t   *ch;
#ifdef __USEA3D
	int ch_first_3d;
	int num3d;

	ch_first_3d = -1;
	if ( snd_isa3d )
		num3d = hA3D_GetNum3DSources();
	else
		num3d = 0;
#endif

	if (entchannel<0)
		Com_Error ( ERR_DROP, "S_PickChannel: entchannel<0" );

	// Check for replacement sound, or find the best one to replace
	first_to_die = -1;
	life_left = 0x7fffffff;

	ch_idx=0;

#ifdef __USEA3D
	// Apply filtering only if a3d is enabled.
	if (!snd_isa3d)
		ch_idx = 0; // Try for a 3D sound.
	else
	// Filter out all non-3d sounds
	if ((entnum < 0)                 ||           // No music
	    (sc && sc->type != WAVE_FORMAT_PCM))      // Can't handle non-PCM wave types.
		ch_idx = num3d;
	else
		ch_idx = 0;     // Try for a 3D sound.
#endif

	for (; ch_idx < MAX_CHANNELS; ch_idx++)
	{
		if (entchannel != 0     // channel 0 never overrides
		    && channels[ch_idx].entnum == entnum
		    && channels[ch_idx].entchannel == entchannel)
		{   // allways override sound from same entity
#ifdef __USEA3D
			// Want to override same entity sounds also.
			// Aggressively move 2D sounds into 3D.
			if (snd_isa3d && ch_idx < num3d)
				ch_first_3d = ch_idx;
#endif

			first_to_die = ch_idx;
			break;
		}

#ifdef __USEA3D
		// Autosounds must be kept within their own channel and never get caught in the
		// restart bug.
		if (snd_isa3d && bUseAutoSettings && channels[ch_idx].entnum == entnum && channels[ch_idx].autosound)
		{
			if (snd_isa3d && ch_idx < num3d)
				ch_first_3d = ch_idx;

			first_to_die = ch_idx;
			break;
		}
#endif

		// don't let music sounds get killed no matter what
		if (channels[ch_idx].entnum < 0 )
			continue;

		// don't let monster sounds override player sounds, guarantee that music will always get a channel
		if (channels[ch_idx].entnum == cl.playernum+1 && entnum > cl.playernum+1 && channels[ch_idx].sfx)
			continue;

#ifdef __USEA3D
		// Don't replace autosounds just because they're autosounds.
		if (snd_isa3d && channels[ch_idx].autosound)
			continue;
#endif

		if (channels[ch_idx].end - paintedtime < life_left)
		{
			life_left = channels[ch_idx].end - paintedtime;
			first_to_die = ch_idx;

#ifdef __USEA3D
			if (snd_isa3d && ch_idx < num3d && (channels[ch_idx].sfx == NULL || life_left < 0))
				ch_first_3d = ch_idx;
#endif
		}
	}

	if (first_to_die == -1)
		return NULL;

#ifdef __USEA3D
	if (snd_isa3d && ch_first_3d >= 0)
	{
		first_to_die = ch_first_3d;
		if (bUseAutoSettings && sfx == channels[first_to_die].sfx)
			hA3D_AssignSourceToChannel ( first_to_die, true );
		else
			hA3D_AssignSourceToChannel ( first_to_die, false );
	}
#endif

	ch = &channels[first_to_die];

	memset ( ch, 0, sizeof(*ch));

	return ch;
}

/*
=================
S_SpatializeOrigin

Used for spatializing channels and autosounds
=================
*/
static void S_SpatializeOrigin( vec3_t origin, float master_vol, float dist_mult, int *left_vol, int *right_vol )
{
	vec_t dot;
	vec_t dist;
	vec_t lscale, rscale, scale;
	vec3_t source_vec;

	if (cls.state != ca_active)
	{
		*left_vol = *right_vol = 255;
		return;
	}

// calculate stereo seperation and distance attenuation
	VectorSubtract( origin, listener_origin, source_vec );

	dist = VectorNormalize( source_vec );
	dist -= SOUND_FULLVOLUME;
	if (dist < 0)
		dist = 0;           // close enough to be at full volume
	dist *= dist_mult;      // different attenuation levels

	dot = DotProduct( listener_right, source_vec );

	if (dma.channels == 1 || !dist_mult)
	{ // no attenuation = no spatialization
		rscale = 1.0;
		lscale = 1.0;
	}
	else
	{
		rscale = 0.5 * (1.0 + dot);
		lscale = 0.5*(1.0 - dot);
	}

	// add in distance effect
	scale = (1.0 - dist) * rscale;
	*right_vol = (int) (master_vol * scale);
	if (*right_vol < 0)
		*right_vol = 0;

	scale = (1.0 - dist) * lscale;
	*left_vol = (int) (master_vol * scale);
	if (*left_vol < 0)
		*left_vol = 0;
}

/*
=================
S_Spatialize
=================
*/
void S_Spatialize( channel_t *ch )
{
	vec3_t origin;

	if (ch->flags & SOUND_SYNCH)
	{
		if (cl_paused->value)
		{
			if (!(ch->flags & SOUND_PAUSED))
			{
				ch->flags |= SOUND_PAUSED;
				ch->fadepos = paintedtime;
			}
		}
		else
		{
			if (ch->flags & SOUND_PAUSED)
			{
				ch->flags &= ~SOUND_PAUSED;
				ch->end += paintedtime-ch->fadepos;
			}
		}
	}
	else if (ch->flags & SOUND_SYNCH_FADE)
	{
		if (cl_paused->value)
		{
			if (!(ch->flags & SOUND_FADING) && !(ch->flags & SOUND_PAUSING) && !(ch->flags & SOUND_PAUSED))
			{
				S_FadeSound( ch, 0.5f, true );
				ch->flags |= SOUND_PAUSING;
			}
		}
		else
		{
			if (((ch->flags & SOUND_FADING) && (ch->flags & SOUND_PAUSING)) || (ch->flags & SOUND_PAUSED))
			{
				ch->flags &= ~SOUND_PAUSING;
				S_FadeSound( ch, 0.5f, false );
				ch->flags &= ~SOUND_PAUSED;
			}
		}
	}

	if (ch->entnum<0)
	{
		if ( s_musicvolume->value >= 0 && s_musicvolume->value <= 1 )
		{
			ch->leftvol = ch->master_vol*s_musicvolume->value;
			ch->rightvol = ch->master_vol*s_musicvolume->value;
#ifdef _ADD_EAX_
			ch->rleftvol = 0;//ch->master_vol*s_musicvolume->value;
			ch->rrightvol = 0;//ch->master_vol*s_musicvolume->value;
#endif
		}
		else
		{
			Com_DPrintf(DEVELOPER_MSG_SOUND, "Clamping music volume\n");
			ch->leftvol = ch->master_vol;
			ch->rightvol = ch->master_vol;
#ifdef _ADD_EAX_
			ch->rleftvol = ch->master_vol;
			ch->rrightvol = ch->master_vol;
#endif
		}
		if (ch->leftvol > 255)
			ch->leftvol = 255;
		if (ch->rightvol > 255)
			ch->rightvol = 255;
#ifdef _ADD_EAX_
		if ( ch->rleftvol > 255 )
			ch->rleftvol = 255;
		if ( ch->rrightvol > 255 )
			ch->rrightvol = 255;
#endif
		return;
	}
	else
	// anything coming from the view entity will allways be full volume
	if (ch->entnum == cl.playernum+1)
	{
		ch->leftvol = ch->master_vol;
		ch->rightvol = ch->master_vol;
#ifdef _ADD_EAX_
		ch->rleftvol = ch->master_vol;
		ch->rrightvol = ch->master_vol;
#endif
		return;
	}

	if (ch->fixed_origin)
	{
		VectorCopy ( ch->origin, origin );
	}
	else
		CL_GetEntitySoundOrigin ( ch->entnum, origin );

#ifdef _ADD_EAX_
	if (EAX_format_set)
	{
		SEAX_SpatializeOrigin( origin, ch->master_vol, ch->dist_mult, &ch->leftvol, &ch->rightvol, &ch->rleftvol, &ch->rrightvol );
		if (ch->leftvol > 255)
			ch->leftvol = 255;
		if (ch->rightvol > 255)
			ch->rightvol = 255;
		if ( ch->rleftvol > 255 )
			ch->rleftvol = 255;
		if ( ch->rrightvol > 255 )
			ch->rrightvol = 255;
		return;
	}
#endif

	S_SpatializeOrigin ( origin, ch->master_vol, ch->dist_mult, &ch->leftvol, &ch->rightvol );
	if (ch->leftvol > 255)
		ch->leftvol = 255;
	if (ch->rightvol > 255)
		ch->rightvol = 255;
}


/*
=================
S_AllocPlaysound
=================
*/
static playsound_t *S_AllocPlaysound( void )
{
	playsound_t *ps;

	ps = s_freeplays.next;
	if (ps == &s_freeplays)
		return NULL;        // no free playsounds

	// unlink from freelist
	ps->prev->next = ps->next;
	ps->next->prev = ps->prev;

	return ps;
}


/*
=================
S_FreePlaysound
=================
*/
static void S_FreePlaysound( playsound_t *ps )
{
	// unlink from channel
	ps->prev->next = ps->next;
	ps->next->prev = ps->prev;

	// add to free list
	ps->next = s_freeplays.next;
	s_freeplays.next->prev = ps;
	ps->prev = &s_freeplays;
	s_freeplays.next = ps;
}


/*
===============
S_IssuePlaysound

Take the next playsound and begin it on the channel
This is never called directly by S_Play*, but only
by the update loop.
===============
*/
void S_IssuePlaysound( playsound_t *ps )
{
	channel_t   *ch;
	sfxcache_t  *sc;

	if (s_show->value)
		Com_Printf ( "Issue %i\n", ps->begin );
	// pick a channel to play on
	// A3D Note: this is not surrounded in "if (snd_isa3d)" because the function
	// declaration has been changed.  However, if snd_isa3d == 0, S_PickChannel
	// functions exactly like it would if __USEA3D were undefined.
#ifdef __USEA3D
	ch = S_PickChannel( ps->entnum, ps->entchannel, false, ps->sfx->cache, ps->sfx );
#else
	ch = S_PickChannel( ps->entnum, ps->entchannel );
#endif
	if (!ch)
	{
		S_FreePlaysound ( ps );
		return;
	}

	// spatialize
	// A3D NOTE: to get the attenuation number, we always divide by 0.0005.
	//				For STATIC sounds, this effectively doubles the rolloff.
	if (ps->attenuation == ATTN_STATIC)
		ch->dist_mult = ps->attenuation * 0.001;
	else
		ch->dist_mult = ps->attenuation * 0.0005;
	ch->master_vol = ps->volume;
	ch->entnum = ps->entnum;
	ch->entchannel = ps->entchannel;
	ch->sfx = ps->sfx;
	VectorCopy ( ps->origin, ch->origin );
	ch->fixed_origin = ps->fixed_origin;

	S_Spatialize( ch );

	sc = S_LoadSound ( ch->sfx );

	ch->pitch = ps->pitch;
	ch->state = ps->state;
	ch->pos = ps->pos;
	if (sc->type == WAV_IMA_ADPCM_FORMAT)
	{
		ch->pos = SAMPLE_TO_SIN_VALUE( ch->state.ima_pos );
	}
	ch->end = paintedtime + SAMPLE_TO_SIN_VALUE( sc->length ) - ch->pos;
	ch->srcstep = (int)SIN_TO_SAMPLE_VALUE( 1<<SND_FRAC_BITS );
	ch->srcfrac = SIN_TO_SAMPLE_VALUE( ch->pos );
	ch->srcfrac <<= SND_FRAC_BITS;
	ch->flags = ps->flags;
	if (ps->fadetime>0)
	{
		ch->fadefrac = 0;
		S_FadeSound( ch, ps->fadetime, false );
	}
	else
	{
		ch->fadefrac = 255<<SND_FRAC_BITS;
	}

	// free the playsound
	S_FreePlaysound ( ps );
}


// =======================================================================
// Start a sound effect
// =======================================================================

/*
====================
S_StartSoundEx

Validates the parms and ques the sound up
if pos is NULL, the sound will be dynamically sourced from the entity
Entchannel 0 will never override a playing sound
pitch is a scalar
fadetime, is time for sound to fade in. (used for music mostly)
====================
*/
void S_StartSoundEx( vec3_t origin, int entnum, int entchannel, struct sfx_s *sfx, float fvol, float attenuation, float timeofs, float pitch, float fadetime, int pos, void * state, int flags )
{
	sfxcache_t  *sc;
	int vol;
	playsound_t *ps, *sort;
	int start;

	if (!sound_started)
		return;

	if (!sfx)
		return;

	// make sure the sound is loaded
	sc = S_LoadSound ( sfx );
	if (!sc)
		return;     // couldn't load the sound's data

	vol = fvol*255;

	// make the playsound_t
	ps = S_AllocPlaysound ();
	if (!ps)
		return;

	if (origin)
	{
		VectorCopy ( origin, ps->origin );
		ps->fixed_origin = true;
	}
	else
		ps->fixed_origin = false;

	ps->entnum = entnum;
	ps->entchannel = entchannel;
	ps->attenuation = attenuation;
	ps->volume = vol;
	ps->sfx = sfx;

	if ( pitch == 1.0f && entnum > 1 )
	{
		float scale;

		// get scale for entity
		scale = CL_GetEntityScale( entnum );
		if ( scale > 3 )
			scale = 3;
		if ( scale < 0.1 )
			scale = 0.1;
		if ( scale > 1 )
		{
			pitch = pitch - ( pitch * ( scale - 1 ) * 0.25f );
		}
		else
		{
			pitch = pitch + ( pitch * ( 1 - scale ) * 1 );
			if ( scale < 0.2 )
				scale = 0.2;
			// scale the volume as well
			ps->volume *= scale;
		}
	}
	if ( (timescale->value > 0) && (!timescalenopitch->value) )
		ps->pitch = pitch*timescale->value;
	else
		ps->pitch = pitch;
	ps->fadetime = fadetime;
	ps->pos = pos;
	if (state)
		ps->state = *(ImaState *)state;
	else
		memset( &ps->state, 0, sizeof( ps->state ) );
	ps->flags = flags;
	if (ps->flags & SOUND_RANDOM_PITCH_20)
	{
		int poff;

		poff = (rand() & 63) - 32;
		ps->pitch = ps->pitch + ps->pitch * ( (float)poff / 320.0f );
	}
	else if (ps->flags & SOUND_RANDOM_PITCH_40)
	{
		int poff;

		poff = (rand() & 63) - 32;
		ps->pitch = ps->pitch + ps->pitch * ( (float)poff / 160.0f );
	}

	// play local sounds directly through the players ears
	if ( ps->flags & SOUND_LOCAL_DIALOG )
	{
		ps->entnum = cl.playernum+1;
	}

	// drift s_beginofs
	start = cl.frame.servertime * 0.001 * dma.speed + s_beginofs;
	if (start < paintedtime)
	{
		start = paintedtime;
		s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed);
	}
	else if (start > paintedtime + 0.3 * dma.speed)
	{
		start = paintedtime + 0.1 * dma.speed;
		s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed);
	}
	else
	{
		s_beginofs-=10;
	}

	if (!timeofs)
		ps->begin = paintedtime;
	else
		ps->begin = start + timeofs * dma.speed;

	// sort into the pending sound list
	for (sort = s_pendingplays.next;
	     sort != &s_pendingplays && sort->begin < ps->begin;
	     sort = sort->next)
		;

	ps->next = sort;
	ps->prev = sort->prev;

	ps->next->prev = ps;
	ps->prev->next = ps;
}


/*
==================
S_StartLocalSound
==================
*/
void S_StartLocalSound( const char *sound )
{
	sfx_t   *sfx;

	if (!sound_started)
		return;

	sfx = S_RegisterSound ( sound );
	if (!sfx)
	{
		Com_Printf ( "S_StartLocalSound: can't cache %s\n", sound );
		return;
	}
	S_StartSound ( NULL, cl.playernum+1, 0, sfx, 1, 1, 0 );
}


/*
==================
S_ClearBuffer
==================
*/
static void S_ClearBuffer( void )
{
	int clear;

	if (!sound_started)
		return;

	s_rawend = 0;

	if (dma.samplebits == 8)
		clear = 0x80;
	else
		clear = 0;

#ifdef _ADD_EAX_
	if ( EAX_format_set )
	{
		SNDEAX_ClearBuffers();
		return;
	}
#endif

	SNDDMA_BeginPainting ();
	if (dma.buffer)
		memset( dma.buffer, clear, dma.samples * dma.samplebits/8 );
	SNDDMA_Submit ();
#ifdef __DJGPP__
	if(havegus)
		GUS_ClearDMA(); /* FS: Added */
#endif
}

/*
==================
S_StopAllSounds
==================
*/
void S_StopAllSounds( void )
{
	int i;

	if (!sound_started)
		return;

	// clear all the playsounds
	memset( s_playsounds, 0, sizeof(s_playsounds));
	s_freeplays.next = s_freeplays.prev = &s_freeplays;
	s_pendingplays.next = s_pendingplays.prev = &s_pendingplays;

	for (i=0; i<MAX_PLAYSOUNDS; i++)
	{
		s_playsounds[i].prev = &s_freeplays;
		s_playsounds[i].next = s_freeplays.next;
		s_playsounds[i].prev->next = &s_playsounds[i];
		s_playsounds[i].next->prev = &s_playsounds[i];
	}

	// clear all the channels
	memset( channels, 0, sizeof(channels));

	// stop the music
	MUSIC_StopAllSongs();

	S_ClearBuffer ();

#ifdef __USEA3D
	if ( snd_isa3d )
		hA3D_StopAllSounds();
#endif
}

/*
==================
S_AddLoopSounds

Entities with a ->sound field will generated looped sounds
that are automatically started, stopped, and merged together
as the entities are sent to the client
==================
*/
static void S_AddLoopSounds( void )
{
	int i, j;
	int sounds[MAX_EDICTS];
	int left, right, left_total, right_total;
	channel_t   *ch;
	sfx_t       *sfx;
	sfxcache_t  *sc;
	int num;
	entity_state_t  *ent;
	float attenuation;
	float dist_mult;
#ifdef _ADD_EAX_
	int rleft, rright, rleft_total, rright_total;
#endif

	if (cl_paused->value)
		return;

	if (cls.state != ca_active)
		return;

	if (!cl.sound_prepped)
		return;

	for (i=0; i<cl.frame.num_entities; i++)
	{
		num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
		ent = &cl_parse_entities[num];
		sounds[i] = ent->sound&0x3fff;
	}

	for (i=0; i<cl.frame.num_entities; i++)
	{
		if (!sounds[i])
			continue;

		sfx = cl.sound_precache[sounds[i]];
		if (!sfx)
			continue;       // bad sound effect
		sc = sfx->cache;
		if (!sc)
			continue;

		num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
		ent = &cl_parse_entities[num];

		// A3D NOTE: to get the attenuation number, we always divide by 0.0005.
		//			For STATIC sounds, this effectively doubles the rolloff.
		attenuation = ((unsigned short)ent->sound >> 14);
		if (attenuation == ATTN_STATIC)
			dist_mult = attenuation * 0.001;
		else
			dist_mult = attenuation * 0.0005;

		// find the total contribution of all sounds of this type
#ifdef _ADD_EAX_
		if ( EAX_format_set )
			SEAX_SpatializeOrigin ( ent->origin, 255.0, dist_mult, &left_total, &right_total, &rleft_total, &rright_total );
		else
#endif
		S_SpatializeOrigin ( ent->origin, 255.0, dist_mult, &left_total, &right_total );

#ifdef __USEA3D
		if (snd_isa3d && (left_total != 0 || right_total != 0))
		{
			ch = S_PickChannel( i, 0,  true, sc, sfx ); // Autolooping sound


			if (ch)
			{
				VectorCopy ( ent->origin, ch->origin );
				ch->leftvol = left_total;
				ch->rightvol = right_total;
				ch->autosound = true;   // remove next frame
				ch->sfx = sfx;

				ch->pitch = 1.0f;
				num = SAMPLE_TO_SIN_VALUE( sc->length );
				ch->pos = paintedtime % num;
				ch->end = paintedtime + num - ch->pos;
				ch->srcstep = (int)SIN_TO_SAMPLE_VALUE( 1<<SND_FRAC_BITS );
				ch->srcfrac = SIN_TO_SAMPLE_VALUE( ch->pos );
				ch->srcfrac <<= SND_FRAC_BITS;

				// For A3D, we want the original attenuation value, not the converted one.  Hence, same multiplier every time.
				ch->master_vol = (left_total + right_total) / 2;

				// For A3D, we want the original attenuation value, not the converted one.  Hence, same multiplier every time.
				ch->dist_mult = dist_mult;
				ch->entnum = i;   // Need this to prevent autosounds from repeatedly restarting.
			}
		}
#endif
		for (j=i+1; j<cl.frame.num_entities; j++)
		{
			if (sounds[j] != sounds[i])
				continue;
			sounds[j] = 0;  // don't check this again later

			num = (cl.frame.parse_entities + j)&(MAX_PARSE_ENTITIES-1);
			ent = &cl_parse_entities[num];

			attenuation = ((unsigned short)ent->sound >> 14);
			if (attenuation == ATTN_STATIC)
				dist_mult = attenuation * 0.001;
			else
				dist_mult = attenuation * 0.0005;
			// find the total contribution of all sounds of this type
////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
			if ( EAX_format_set )
				SEAX_SpatializeOrigin( ent->origin, 255.0, dist_mult, &left, &right, &rleft, &rright );
			else
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////
			S_SpatializeOrigin ( ent->origin, 255.0, dist_mult, &left, &right );
#ifdef __USEA3D
			if (snd_isa3d && (left != 0 || right != 0))
			{
				// We need a unique id for each autosound.  So, we'll use it's memory address.
				ch = S_PickChannel( j, 0,  true, sc, sfx ); // Autolooping sound

				if (ch)
				{
					VectorCopy ( ent->origin, ch->origin );
					ch->leftvol = left;
					ch->rightvol = right;
					ch->autosound = true;   // remove next frame
					ch->sfx = sfx;

					ch->pitch = 1.0f;
					num = SAMPLE_TO_SIN_VALUE( sc->length );
					ch->pos = paintedtime % num;
					ch->end = paintedtime + num - ch->pos;
					ch->srcstep = (int)SIN_TO_SAMPLE_VALUE( 1<<SND_FRAC_BITS );
					ch->srcfrac = SIN_TO_SAMPLE_VALUE( ch->pos );
					ch->srcfrac <<= SND_FRAC_BITS;

					// For A3D, we want the original attenuation value, not the converted one.  Hence, same multiplier every time.
					ch->master_vol = (left + right) / 2;

					// For A3D, we want the original attenuation value, not the converted one.  Hence, same multiplier every time.
					ch->dist_mult = dist_mult;
					ch->entnum = j;   // Need this to prevent autosounds from repeatedly restarting.
				}
			}
#endif
#ifdef _ADD_EAX_
			if ( EAX_format_set )
			{
				left_total += left;
				right_total += right;
				rleft_total += rleft;
				rright_total += rright;
			}
			else
			{
#endif
			left_total += left;
			right_total += right;

#ifdef _ADD_EAX_
		}
#endif
		}

#ifdef _ADD_EAX_
		if ( EAX_format_set )
		{
			if (left_total == 0 && right_total == 0 && rleft_total == 0 && rright_total == 0)
				continue;
		}
		else
#endif
		if (left_total == 0 && right_total == 0)
			continue;           // not audible


		// allocate a channel
		// A3D Note: this is not surrounded in "if (snd_isa3d)" because the function
		// declaration has been changed.  However, if snd_isa3d == 0, S_PickChannel
		// functions exactly like it would if __USEA3D were undefined.
#ifdef __USEA3D
		// Non-a3d handles autosounds differently.
		if (!snd_isa3d)
			ch = S_PickChannel( 0, 0, true, sc, NULL );
		else
			continue;
#else
		ch = S_PickChannel( 0, 0 );
#endif
		if (!ch)
			return;

#ifdef _ADD_EAX_
		if ( EAX_format_set )
		{
			if (left_total > 255)
				left_total = 255;
			if (right_total > 255)
				right_total = 255;
			if (rleft_total > 255)
				rleft_total = 255;
			if (rright_total > 255)
				rright_total = 255;
			ch->leftvol = left_total;
			ch->rightvol = right_total;
			ch->rleftvol = rleft_total;
			ch->rrightvol = rright_total;
		}
		else
		{
#endif
		if (left_total > 255)
			left_total = 255;
		if (right_total > 255)
			right_total = 255;
		ch->leftvol = left_total;
		ch->rightvol = right_total;
#ifdef _ADD_EAX_
	}
#endif
		ch->autosound = true;   // remove next frame
		ch->sfx = sfx;
		ch->pitch = 1.0f;
		num = SAMPLE_TO_SIN_VALUE( sc->length );
		ch->pos = paintedtime % num;
		ch->end = paintedtime + num - ch->pos;
		ch->srcstep = (int)SIN_TO_SAMPLE_VALUE( 1<<SND_FRAC_BITS );
		ch->srcfrac = SIN_TO_SAMPLE_VALUE( ch->pos );
		ch->srcfrac <<= SND_FRAC_BITS;
	}
}

//=============================================================================

/*
============
S_RawSamples

Cinematic streaming and voice over network
============
*/
void S_RawSamples( int samples, int rate, int width, int channels, byte *data )
{
	int i;
	int src, dst;
	float scale;

	if (!sound_started)
		return;

	if (s_rawend < paintedtime)
		s_rawend = paintedtime;
	scale = (float)rate / dma.speed;

//Com_Printf ("%i < %i < %i\n", soundtime, paintedtime, s_rawend);
	if (channels == 2 && width == 2)
	{
		if (scale == 1.0)
		{   // optimized case
			for (i=0; i<samples; i++)
			{
				dst = s_rawend&(MAX_RAW_SAMPLES-1);
				s_rawend++;
				s_rawsamples[dst].left =
					LittleShort(((short *)data)[i*2] ) << 8;
				s_rawsamples[dst].right =
					LittleShort(((short *)data)[i*2+1] ) << 8;
			}
		}
		else
		{
			for (i=0;; i++)
			{
				src = i*scale;
				if (src >= samples)
					break;
				dst = s_rawend&(MAX_RAW_SAMPLES-1);
				s_rawend++;
				s_rawsamples[dst].left =
					LittleShort(((short *)data)[src*2] ) << 8;
				s_rawsamples[dst].right =
					LittleShort(((short *)data)[src*2+1] ) << 8;
			}
		}
	}
	else if (channels == 1 && width == 2)
	{
		for (i=0;; i++)
		{
			src = i*scale;
			if (src >= samples)
				break;
			dst = s_rawend&(MAX_RAW_SAMPLES-1);
			s_rawend++;
			s_rawsamples[dst].left = LittleShort(((short *)data)[src] ) << 8;
			s_rawsamples[dst].right = LittleShort(((short *)data)[src] ) << 8;
		}
	}
	else if (channels == 2 && width == 1)
	{
		for (i=0;; i++)
		{
			src = i*scale;
			if (src >= samples)
				break;
			dst = s_rawend&(MAX_RAW_SAMPLES-1);
			s_rawend++;
			s_rawsamples[dst].left = ((byte *)data)[src*2] << 16;
			s_rawsamples[dst].right = ((byte *)data)[src*2+1] << 16;
		}
	}
	else if (channels == 1 && width == 1)
	{
		for (i=0;; i++)
		{
			src = i*scale;
			if (src >= samples)
				break;
			dst = s_rawend&(MAX_RAW_SAMPLES-1);
			s_rawend++;
			s_rawsamples[dst].left = (((byte *)data)[src]-128) << 16;
			s_rawsamples[dst].right = (((byte *)data)[src]-128) << 16;
		}
	}
}

static void GetSoundtime( void )
{
	int samplepos;
	static int buffers;
	static int oldsamplepos;
	int fullsamples;

	fullsamples = dma.samples / dma.channels;

// it is possible to miscount buffers if it has wrapped twice between
// calls to S_Update.  Oh well.
	samplepos = SNDDMA_GetDMAPos();

	if (samplepos < oldsamplepos)
	{
		buffers++;                  // buffer wrapped

		if (paintedtime > 0x40000000)
		{   // time to chop things off to avoid 32 bit limits
			buffers = 0;
			paintedtime = fullsamples;
			S_StopAllSounds ();
		}
	}
	oldsamplepos = samplepos;

	soundtime = buffers*fullsamples + samplepos/dma.channels;
}

//=============================================================================

static void S_Update_( void )
{
	unsigned endtime;
	int samps;

	if (!sound_started)
		return;

#ifdef _ADD_EAX_
	if ( !EAX_format_set )
	{
#endif
	SNDDMA_BeginPainting ();

	if (!dma.buffer)
		return;
#ifdef _ADD_EAX_
}
#endif

// Updates DMA time
	GetSoundtime();

// check to make sure that we haven't overshot
	if (paintedtime < soundtime)
	{
		Com_DPrintf (DEVELOPER_MSG_GENERIC_VERBOSE, "S_Update_ : overflow\n");
		paintedtime = soundtime;
	}

// mix ahead of current position
	endtime = soundtime + s_mixahead->value * dma.speed;
//endtime = (soundtime + 4096) & ~4095;

	// mix to an even submission block size
	endtime = (endtime + dma.submission_chunk-1)
	          & ~(dma.submission_chunk-1);
	samps = dma.samples >> (dma.channels-1);
	if (endtime - soundtime > samps)
		endtime = soundtime + samps;

	S_PaintChannels ( endtime );

	SNDDMA_Submit ();
}

/*
============
S_Update

Called once each time through the main loop
============
*/
void S_Update( vec3_t origin, vec3_t forward, vec3_t right, vec3_t up )
{
	int i;
	int total;
	channel_t   *ch;
//	channel_t   *combine;

	if (!sound_started)
		return;

	// if the laoding plaque is up, clear everything
	// out to make sure we aren't looping a dirty
	// dma buffer while loading
	if (cls.disable_screen)
	{
		S_ClearBuffer ();
		return;
	}

	// rebuild scale tables if volume is modified
#ifdef __USEA3D
	if (snd_isa3d)
	{
		if (s_volume->modified)
		{
			A3D_SetMainVolume( s_volume->value );
			s_volume->modified = false;
		}
	}
	else
#endif // __USEA3D
	{
		if ((s_volume->modified)
			|| (s_musicvolume->modified)
			|| (s_dialogvolume->modified))
		{
			S_InitScaletable ();
		}
	}

	VectorCopy( origin, listener_origin );
	VectorCopy( forward, listener_forward );
	VectorCopy( right, listener_right );
	VectorCopy( up, listener_up );

#ifdef __USEA3D
	if (snd_isa3d)
	{
		A3D_SetListenerPosition ( origin );
		A3D_SetListenerOrientation( forward, up );
	}
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
	if ( EAX_format_set )
	{
		vec3_t mins, maxs;

		if ( CL_CalculateViewVolume( mins, maxs ) )
		{
			vec3_t delta;
			float dim;
			float brightness;
			int surftype;
			qboolean inwater;
			qboolean mutant;

			VectorSubtract( maxs, mins, delta );
			dim = ( delta[ 0 ] + delta[ 1 ] + delta[ 2 ] ) / 3.0f;
			//length = VectorLength( delta );
			//Com_Printf ("Room Dim = %.2f\n", dim);
			brightness = 1.0f;
			inwater = false;
			if ( CL_ClassifyViewerPosition( &surftype, &inwater, &mutant ) )
			{
				// see if we are standing in water
				switch ( surftype )
				{
				case SURF_TYPE_GRAVEL:
				case SURF_TYPE_DIRT:
					brightness = 0.5f;
					break;
				case SURF_TYPE_FABRIC:
				case SURF_TYPE_FLESH:
				case SURF_TYPE_VEGETATION:
					brightness = 0.25f;
					break;
				case SURF_TYPE_GRILL:
				case SURF_TYPE_METAL:
					brightness = 1.25f;
					break;
				case SURF_TYPE_STONE:
				case SURF_TYPE_CONCRETE:
					brightness = 1.5f;
					break;
				case SURF_TYPE_DUCT:
					brightness = 2;
				}
			}
			if ( inwater )
			{
				SNDEAX_SetEnvironment( EAX_ENVIRONMENT_UNDERWATER );
			}
			else if ( mutant  )
			{
				SNDEAX_SetEnvironment( EAX_ENVIRONMENT_PSYCHOTIC );
			}
			else
			{
				if ( dim < 100 )
				{
					SNDEAX_SetEnvironment( EAX_ENVIRONMENT_SEWERPIPE );
				}
				else if ( dim < 400 )
				{
					if ( brightness > 1 )
						SNDEAX_SetEnvironment( EAX_ENVIRONMENT_STONEROOM );
					else if ( brightness < 1 )
						SNDEAX_SetEnvironment( EAX_ENVIRONMENT_ROOM );
					else
						SNDEAX_SetEnvironment( EAX_ENVIRONMENT_LIVINGROOM );
				}
				else if ( dim < 700 )
				{
					if ( brightness < 1 )
						SNDEAX_SetEnvironment( EAX_ENVIRONMENT_ARENA );
					else
						SNDEAX_SetEnvironment( EAX_ENVIRONMENT_HANGAR );
				}
				else if ( dim < 1000 )
				{
					SNDEAX_SetEnvironment( EAX_ENVIRONMENT_AUDITORIUM );
				}
				else if ( dim < 1500 )
				{
					SNDEAX_SetEnvironment( EAX_ENVIRONMENT_CONCERTHALL );
				}
				else if ( dim < 2000 )
				{
					SNDEAX_SetEnvironment( EAX_ENVIRONMENT_CITY );
				}
				else
				{
					SNDEAX_SetEnvironment( EAX_ENVIRONMENT_PLAIN );
				}
			}
			SNDEAX_SetEnvironmentLevel( s_eax_reverblevel->value );
		}
	}
#endif

//	combine = NULL;

	// update spatialization for dynamic sounds
	ch = channels;
	for (i=0; i<MAX_CHANNELS; i++, ch++)
	{
		if (!ch->sfx)
		{
#ifdef __USEA3D
			if (snd_isa3d)
				hA3D_PauseBuffer ( i );
#endif
			continue;
		}
		if (ch->autosound)
		{   // autosounds are regenerated fresh each frame
#ifdef __USEA3D
			if (snd_isa3d)
			{
				// For A3D, we cannot deal with autosounds being completely regenerated every frame.
				// We must keep track of what channel they are in so they don't restart in hardware.
				// Also need to keep track of sfx in order to reset the channel when it changes.
				int num = ch->entnum;
				sfx_t *sfx = ch->sfx;

				memset ( ch, 0, sizeof(*ch));
				ch->entnum = num;
				ch->sfx = sfx;
				ch->autosound = true;
				ch->pos = -1; // Indicate this is was an autosound.
				continue;
			}
#endif
			memset ( ch, 0, sizeof(*ch));
			continue;
		}
		S_Spatialize( ch );         // respatialize channel
//////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
		if ( EAX_format_set )
		{
			if (!ch->leftvol && !ch->rightvol && !ch->rleftvol && !ch->rrightvol)
			{
				memset ( ch, 0, sizeof(*ch));
				continue;
			}
		}
		else
		{
#endif
//////////////////////////////////////////////////////////////////////////////////
		if (!ch->leftvol && !ch->rightvol)
		{
			memset ( ch, 0, sizeof(*ch));
			continue;
		}
//////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
	}
#endif
//////////////////////////////////////////////////////////////////////////////////
	}

	// add loopsounds
	S_AddLoopSounds ();

	//
	// debugging output
	//
	if (s_show->value)
	{
		total = 0;
		ch = channels;
		for (i=0; i<MAX_CHANNELS; i++, ch++)
			if (ch->sfx && (ch->leftvol || ch->rightvol) )
			{
				Com_Printf ( "%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name );
				total++;
			}

		Com_Printf ( "----(%i)---- painted: %i\n", total, paintedtime );
	}

// mix some sound
	S_Update_();
}

void S_PlayMusic( void )
{
	char name[256];

	strcpy( name, Cmd_Argv( 1 ));
	MUSIC_PlaySong( name );
}

void S_LoadMusic( void )
{
	char name[256];

	strcpy( name, Cmd_Argv( 1 ));
	CL_NewSoundtrack( name );
	// MUSIC_LoadSoundtrackFile( name );
	// MUSIC_RegisterSongs();
	// MUSIC_PlaySong( "normal" );
}

/*
============
S_FadeSound

Called to setup sound fading
============
*/
void S_FadeSound( channel_t * ch, float fadetime, qboolean fadeout )
{
	ch->flags |= SOUND_FADING;
	ch->fadestep = (1<<(SND_FRAC_BITS+8))/(fadetime * dma.speed);
	if (ch->fadestep < 1)
		ch->fadestep = 1;
	if (fadeout)
		ch->fadestep = -ch->fadestep;
	ch->fadepos = paintedtime;
}

/*
==================
S_SoundLength
==================
*/
qboolean S_SoundLength( const char *name, float * time )
{
	sfx_t *sfx;

	if (!sound_started)
		return false;

	sfx = S_FindName ( name );

	if ( sfx->cache )
	{
		*time = ( float )sfx->cache->length / ( float )sfx->cache->speed;
		return true;
	}
	return false;
}


////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _ADD_EAX_
void SEAX_SpatializeOrigin( vec3_t origin, float master_vol, float dist_mult, int *left_vol, int *right_vol, int *rleft_vol, int *rright_vol )
{
	vec_t dot, dot2;
	vec_t dist;
	vec_t scale, lscale, rscale, lfscale, rfscale, lrscale, rrscale;
	vec3_t source_vec;

	if (cls.state != ca_active)
	{
		*left_vol = *right_vol =  128;
		*rleft_vol =  *rright_vol = 128;
		return;
	}

	VectorSubtract( origin, listener_origin, source_vec );

	dist = VectorNormalize( source_vec );
	dist -= SOUND_FULLVOLUME;
	if (dist < 0) dist = 0;
	dist *= dist_mult;

	dot = DotProduct( listener_right, source_vec );
	dot2 = DotProduct( listener_forward, source_vec );

	if (dma.channels == 1 || !dist_mult)
	{
		rscale = 1.0;
		lscale = 1.0;
	}
	else
	{
		rscale = 1.0 + dot;
		lscale = 1.0 - dot;
	}

	rfscale = (rscale * (1.0 + dot2))/2;
	rrscale = (rscale * (1.0 - dot2))/2;
	lfscale = (lscale * (1.0 + dot2))/2;
	lrscale = (lscale * (1.0 - dot2))/2;

	scale = (1.0 - dist) * rfscale;
	*right_vol = (int) (master_vol * scale);
	if (*right_vol < 0) *right_vol = 0;

	scale = (1.0 - dist) * lfscale;
	*left_vol = (int) (master_vol * scale);
	if (*left_vol < 0) *left_vol = 0;

	scale = (1.0 - dist) * rrscale;
	*rright_vol = (int) (master_vol * scale);
	if (*rright_vol < 0) *rright_vol = 0;

	scale = (1.0 - dist) * lrscale;
	*rleft_vol = (int) (master_vol * scale);
	if (*rleft_vol < 0) *rleft_vol = 0;
}
#endif
