/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code 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.

Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

/*****************************************************************************
 * name:		snd_dma.c
 *
 * desc:		main control for any streaming sound output device
 *
 * $Archive: /MissionPack/code/client/snd_dma.c $
 *
 *****************************************************************************/

#include "snd_local.h"
#include "snd_codec.h"
#include "client.h"

static void S_Update_( void );
static void S_Base_StopAllSounds(void);
static void S_Base_StopBackgroundTrack( void );
static void S_memoryLoad(sfx_t *sfx);
static void S_UpdateBackgroundTrack (void);


static snd_stream_t	*s_backgroundStream = NULL;
static char		s_backgroundLoop[MAX_QPATH];
//static char		s_backgroundMusic[MAX_QPATH]; //TTimo: unused


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

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

#define		SOUND_ATTENUATE		0.0008f

channel_t   s_channels[MAX_CHANNELS];
channel_t   loop_channels[MAX_CHANNELS];
int			numLoopChannels;

static int	s_soundStarted;
static		qboolean	s_soundMuted;

dma_t		dma;

static int			listener_number;
static vec3_t		listener_origin;
static vec3_t		listener_axis[3];

int			s_soundtime;		// sample PAIRS
int   		s_paintedtime; 		// sample PAIRS

// MAX_SFX may be larger than MAX_SOUNDS because
// of custom player sounds
#define		MAX_SFX			4096
sfx_t		s_knownSfx[MAX_SFX];
int			s_numSfx = 0;

#define		LOOP_HASH		128
static	sfx_t		*sfxHash[LOOP_HASH];

cvar_t		*s_testsound;
cvar_t		*s_show;
cvar_t		*s_mixahead;
cvar_t		*s_mixPreStep;


static loopSound_t		loopSounds[MAX_LOOP_SOUNDS];
static	channel_t		*freelist = NULL;

int						s_rawend[MAX_RAW_STREAMS];
portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];


// ====================================================================
// User-setable variables
// ====================================================================


static void S_Base_SoundInfo(void) {
	Com_Printf("----- Sound Info -----\n" );
	if (!s_soundStarted) {
		Com_Printf ("sound system not started\n");
	} else {
		Com_Printf("%5d channels\n", dma.channels);
		Com_Printf("%5d samples\n", dma.samples);
		Com_Printf("%5d samplebits (%s)\n", dma.samplebits, dma.isfloat ? "float" : "int");
		Com_Printf("%5d submission_chunk\n", dma.submission_chunk);
		Com_Printf("%5d speed\n", dma.speed);
		Com_Printf("%p dma buffer\n", dma.buffer);
		if ( s_backgroundStream ) {
			Com_Printf("Background file: %s\n", s_backgroundLoop );
		} else {
			Com_Printf("No background file.\n" );
		}

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


#ifdef USE_VOIP
static
void S_Base_StartCapture( void )
{
	SNDDMA_StartCapture();
}

static
int S_Base_AvailableCaptureSamples( void )
{
	return SNDDMA_AvailableCaptureSamples();
}

static
void S_Base_Capture( int samples, byte *data )
{
	SNDDMA_Capture(samples, data);
}

static
void S_Base_StopCapture( void )
{
	SNDDMA_StopCapture();
}

static
void S_Base_MasterGain( float val )
{
	SNDDMA_MasterGain(val);
}
#endif



/*
=================
S_Base_SoundList
=================
*/
static void S_Base_SoundList( void ) {
	int		i;
	const sfx_t *sfx;
	int		size, total;
	const char *s;

	total = 0;
	for (sfx = s_knownSfx, i = 0;  i < s_numSfx;  i++, sfx++) {
		size = sfx->soundLength;
		total += size;

		Com_Printf("%03d %6i", i, size);
		switch (sfx->soundCompressionMethod) {
		case SND_COMPRESSION_16BIT:
			s = "16bit";
			break;
		case SND_COMPRESSION_ADPCM:
			s = "adpcm";
			break;
		case SND_COMPRESSION_DAUB4:
			s = "daub4";
			break;
		case SND_COMPRESSION_MULAW:
			s = "mulaw";
			break;
		default:
			s = "unknown";
			break;
		}
		Com_Printf("[%s] : %s", s, sfx->soundName);

		if (sfx->inMemory) {
			s = "resident";
		} else {
			s = "paged out";
		}
		Com_Printf("[%s]\n", s);
	}

	Com_Printf("Total resident: %i\n", total);
	S_DisplayFreeMemory();
}


static void S_ChannelFree(channel_t *v) {
	v->thesfx = NULL;
	*(channel_t **)v = freelist;
	freelist = (channel_t*)v;
}

static channel_t*	S_ChannelMalloc( void ) {
	channel_t *v;
	if (freelist == NULL) {
		return NULL;
	}
	v = freelist;
	freelist = *(channel_t **)freelist;
	v->allocTime = S_Milliseconds();
	return v;
}

static void S_ChannelSetup( void ) {
	channel_t *p, *q;

	// clear all the sounds so they don't
	Com_Memset( s_channels, 0, sizeof( s_channels ) );

	p = s_channels;;
	q = p + MAX_CHANNELS;
	while (--q > p) {
		*(channel_t **)q = q-1;
	}

	*(channel_t **)q = NULL;
	freelist = p + MAX_CHANNELS - 1;
	Com_DPrintf("Channel memory manager started\n");
}



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

/*
================
return a hash value for the sfx name
================
*/
static long S_HashSFXName(const char *name) {
	int		i;
	long	hash;
	char	letter;

	hash = 0;
	i = 0;
	while (name[i] != '\0') {
		letter = tolower(name[i]);
		if (letter =='.') break;				// don't include extension
		if (letter =='\\') letter = '/';		// damn path names
		hash+=(long)(letter)*(i+119);
		i++;
	}
	hash &= (LOOP_HASH-1);
	return hash;
}

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

Will allocate a new sfx if it isn't found
==================
*/
static sfx_t *S_FindName( const char *name ) {
	int		i;
	int		hash;

	sfx_t	*sfx;

	if (!name) {
		Com_Error(ERR_FATAL, "Sound name is NULL");
	}

	if (!name[0]) {
		Com_Printf(S_COLOR_YELLOW "WARNING: Sound name is empty\n");
		return NULL;
	}

	if (strlen(name) >= MAX_QPATH) {
		Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", name );
		return NULL;
	}

	if (name[0] == '*') {
		Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", name );
		return NULL;
	}

	hash = S_HashSFXName(name);

	sfx = sfxHash[hash];
	// see if already loaded
	while (sfx) {
		if (!Q_stricmp(sfx->soundName, name) ) {
			return sfx;
		}
		sfx = sfx->next;
	}

	// find a free sfx
	for (i=0 ; i < s_numSfx ; i++) {
		if (!s_knownSfx[i].soundName[0]) {
			break;
		}
	}

	if (i == s_numSfx) {
		if (s_numSfx >= MAX_SFX) {
			Com_Error (ERR_FATAL, "S_FindName: out of sfx_t");
		}
		s_numSfx++;
	}
	
	sfx = &s_knownSfx[i];
	Com_Memset (sfx, 0, sizeof(*sfx));
	strcpy (sfx->soundName, name);

	sfx->next = sfxHash[hash];
	sfxHash[hash] = sfx;

	return sfx;
}

#if 0
/*
=================
S_DefaultSound
=================
*/
static void S_DefaultSound( sfx_t *sfx ) {
	int		i;

	sfx->soundLength = 512;
	sfx->soundData = SND_malloc();
	sfx->soundData->next = NULL;


	for ( i = 0 ; i < sfx->soundLength ; i++ ) {
		sfx->soundData->sndChunk[i] = i;
	}
}
#endif

/*
===================
S_Base_DisableSounds

Disables sounds until the next S_BeginRegistration.
This is called when the hunk is cleared and the sounds
are no longer valid.
===================
*/
static void S_Base_DisableSounds( void ) {
	S_Base_StopAllSounds();
	s_soundMuted = qtrue;
}

/*
==================
S_Base_RegisterSound

Creates a default buzz sound if the file can't be loaded
==================
*/
static sfxHandle_t	S_Base_RegisterSound( const char *name, qboolean compressed ) {
	sfx_t	*sfx;

	compressed = qfalse;
	if (!s_soundStarted) {
		return 0;
	}

	sfx = S_FindName( name );
	if (!sfx) {
		return 0;
	}

	if ( sfx->soundData ) {
		if ( sfx->defaultSound ) {
			//Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
			return 0;
		}
		return sfx - s_knownSfx;
	}

	sfx->inMemory = qfalse;
	sfx->soundCompressed = compressed;

  S_memoryLoad(sfx);

	if ( sfx->defaultSound ) {
		//Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
		return 0;
	}

	return sfx - s_knownSfx;
}

/*
=====================
S_Base_BeginRegistration

=====================
*/
static void S_Base_BeginRegistration( void ) {
	s_soundMuted = qfalse;		// we can play again

	if (s_numSfx == 0) {
		SND_setup();

		Com_Memset(s_knownSfx, '\0', sizeof(s_knownSfx));
		Com_Memset(sfxHash, '\0', sizeof(sfx_t *) * LOOP_HASH);

		//S_Base_RegisterSound("sound/feedback/hit.wav", qfalse);		// changed to a sound in baseq3
		if (s_debugMissingSounds->integer) {
			S_Base_RegisterSound("sound/wc/beep05", qfalse);
			//S_Base_RegisterSound("sound/wc/a880", qfalse);
		} else {
			S_Base_RegisterSound("silence.wav", qfalse);
		}
	}
}

static void S_memoryLoad(sfx_t	*sfx) {
	// load the sound file
	if ( !S_LoadSound ( sfx ) ) {
//		Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName );
		sfx->defaultSound = qtrue;
	}
	sfx->inMemory = qtrue;
}

static void S_Base_PrintSfxFilename (sfxHandle_t sfx)
{
	const sfx_t *s;

	if (sfx >= s_numSfx  ||  sfx < 0) {
		Com_Printf("^3S_PrintSfxFilename() invalid handle: %d\n", sfx);
	}

	s = &s_knownSfx[sfx];

	Com_Printf("'%s'\n", s->soundName);
}

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

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

Used for spatializing s_channels
=================
*/
static void S_SpatializeOrigin (const vec3_t origin, int master_vol, int *left_vol, int *right_vol)
{
    vec_t		dot;
    vec_t		dist;
    vec_t		lscale, rscale, scale;
    vec3_t		source_vec;
    vec3_t		vec;

	const float dist_mult = SOUND_ATTENUATE;

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

	dist = VectorNormalize(source_vec);
	//dist = VectorLength(source_vec);
	//Com_Printf("dist %f  %f\n", dist, Distance(listener_origin, origin));

	if (!s_qlAttenuate->integer) {  // old q3 code
		dist -= SOUND_FULLVOLUME;
		if (dist < 0) {
			dist = 0;			// close enough to be at full volume
		}
	}

	dist *= dist_mult;		// different attenuation levels

	VectorRotate( source_vec, listener_axis, vec );

	dot = -vec[1];
	//Com_Printf("dot: %f\n", dot);

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

		if ( rscale < 0 ) {
			rscale = 0;
		}
		if ( lscale < 0 ) {
			lscale = 0;
		}
	}

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

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

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

/*
=================
S_Base_HearingThroughEntity

Also see S_AL_HearingThroughEntity
=================
*/
static qboolean S_Base_HearingThroughEntity( int entityNum, const vec3_t origin )
{
	float	distanceSq;
	vec3_t	sorigin;

	if (origin)
		VectorCopy(origin, sorigin);
	else
		VectorCopy(loopSounds[entityNum].origin, sorigin);

	if( listener_number == entityNum )
	{
		// This is an outrageous hack to detect
		// whether or not the player is rendering in third person or not. We can't
		// ask the renderer because the renderer has no notion of entities and we
		// can't ask cgame since that would involve changing the API and hence mod
		// compatibility. I don't think there is any way around this, but I'll leave
		// the FIXME just in case anyone has a bright idea.
		distanceSq = DistanceSquared(
				sorigin,
				listener_origin );

		if( distanceSq > THIRD_PERSON_THRESHOLD_SQ )
			return qfalse; //we're the player, but third person
		else
			return qtrue;  //we're the player
	}
	else
		return qfalse; //not the player
}

/*
====================
S_Base_StartSoundEx

Validates the parms and ques the sound up
if origin is NULL, the sound will be dynamically sourced from the entity
Entchannel 0 will never override a playing sound
====================
*/
static void S_Base_StartSoundEx (const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, qboolean localSound) {
	channel_t	*ch;
	sfx_t		*sfx;
  int i, oldest, chosen, time;
  int	inplay, allowed;
  qboolean fullVolume;

	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	if ( !origin && ( entityNum < 0 || entityNum >= MAX_GENTITIES ) ) {
		Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum );
	}

	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
		Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle );
		return;
	}

	sfx = &s_knownSfx[ sfxHandle ];

	if (sfx->inMemory == qfalse) {
		S_memoryLoad(sfx);
	}

	if ( s_show->integer == 1 ) {
		Com_Printf("%f  %i : %s  (%d -> %d)\n", (float)cl.serverTime + Overf, s_paintedtime, sfx->soundName, listener_number, entityNum);
	}

	time = S_Milliseconds();

//	Com_Printf("playing %s\n", sfx->soundName);
	// pick a channel to play on

	allowed = s_maxSoundInstances->integer;
	if (allowed < 0) {  // old q3 code
		allowed = 4;

		if (entityNum == listener_number) {
			allowed = 8;
		}
	}

	fullVolume = qfalse;
	if (localSound || S_Base_HearingThroughEntity(entityNum, origin)) {
		fullVolume = qtrue;
	}

	ch = s_channels;
	inplay = 0;
	for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) {
		if (ch->entnum == entityNum && ch->thesfx == sfx) {
			if (time - ch->allocTime < 50) {
				//Com_Printf("^2%f double sound start '%s' last played (%d)  length %f\n", (float)cl.serverTime + Overf, sfx->soundName, time - ch->allocTime, (float)sfx->soundLength / (float)dma.speed);
			}

			if (time - ch->allocTime < s_maxSoundRepeatTime->integer) {
			//if (time - ch->allocTime < (int)((float)sfx->soundLength / (float)dma.speed * 1000.0)) {
				if (s_showMiss->integer) {
					Com_Printf("^3%f double sound start '%s' last played (%d)  length %f\n", (float)cl.serverTime + Overf, sfx->soundName, time - ch->allocTime, (float)sfx->soundLength / (float)dma.speed);
				}
				return;
			}
			inplay++;
		}
	}

	//Com_Printf("inplay %d\n", inplay);

	if (inplay > 4) {
		//Com_Printf("^5%f inplay > x  %d > %d  '%s'\n", (float)cl.serverTime + Overf, inplay, allowed, sfx->soundName);
	}

	if (inplay > allowed) {
		if (s_showMiss->integer > 1) {
			Com_Printf("^1%f inplay > allowed  %d > %d  '%s' ent %d ch %d/%d\n", (float)cl.serverTime + Overf, inplay, allowed, sfx->soundName, entityNum, entchannel, listener_number);
		}
		return;
	}

	sfx->lastTimeUsed = time;

	ch = S_ChannelMalloc();	// entityNum, entchannel);
	if (!ch) {
		if (s_showMiss->integer > 2) {
			Com_Printf("^1%f %s() couldn't allocate channel\n", (float)cl.serverTime + Overf, __FUNCTION__);
		}
		ch = s_channels;

		oldest = sfx->lastTimeUsed;
		chosen = -1;
		for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
			if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
				oldest = ch->allocTime;
				chosen = i;
			}
		}
		if (chosen == -1) {
			ch = s_channels;
			for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
				if (ch->entnum != listener_number && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
					oldest = ch->allocTime;
					chosen = i;
				}
			}
			if (chosen == -1) {
				ch = s_channels;
				if (ch->entnum == listener_number) {
					for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
						if (ch->allocTime<oldest) {
							oldest = ch->allocTime;
							chosen = i;
						}
					}
				}
				if (chosen == -1) {
					Com_Printf("dropping sound\n");
					return;
				}
			}
		}
		ch = &s_channels[chosen];
		ch->allocTime = sfx->lastTimeUsed;
		//Com_Printf("use old\n");
	} else {
		//Com_Printf("alloc\n");
	}

	if (origin) {
		VectorCopy (origin, ch->origin);
		ch->fixed_origin = qtrue;
	} else {
		ch->fixed_origin = qfalse;
	}

	ch->master_vol = 127;
	ch->leftvol = 127;
	ch->rightvol = 127;
	ch->entnum = entityNum;
	ch->thesfx = sfx;
	ch->startSample = START_SAMPLE_IMMEDIATE;
	ch->entchannel = entchannel;
	ch->leftvol = ch->master_vol;		// these will get calced at next spatialize
	ch->rightvol = ch->master_vol;		// unless the game isn't running
	ch->doppler = qfalse;
	ch->fullVolume = fullVolume;

	if (ch->entnum != listener_number) {
		if (ch->fixed_origin) {
			S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol);
		}
	} else {
		//Com_Printf("dont respa!!!\n");
	}
}

/*
====================
S_Base_StartSound

if origin is NULL, the sound will be dynamically sourced from the entity
====================
*/
void S_Base_StartSound( const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) {
	S_Base_StartSoundEx( origin, entityNum, entchannel, sfxHandle, qfalse );
}

/*
==================
S_Base_StartLocalSound
==================
*/
static void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) {
	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
		Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle );
		return;
	}

	S_Base_StartSoundEx( NULL, listener_number, channelNum, sfxHandle, qtrue );
}


/*
==================
S_Base_ClearSoundBuffer

If we are about to perform file access, clear the buffer
so sound doesn't stutter.
==================
*/
static void S_Base_ClearSoundBuffer( void ) {
	int		clear;
		
	if (!s_soundStarted)
		return;

	// stop looping sounds
	Com_Memset(loopSounds, 0, MAX_LOOP_SOUNDS*sizeof(loopSound_t));
	Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t));
	numLoopChannels = 0;

	S_ChannelSetup();

	Com_Memset(s_rawend, '\0', sizeof (s_rawend));

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

	SNDDMA_BeginPainting ();
	if (dma.buffer)
		Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8);
	SNDDMA_Submit ();
}

/*
==================
S_Base_StopAllSounds
==================
*/
static void S_Base_StopAllSounds(void) {
	if ( !s_soundStarted ) {
		return;
	}

	// stop the background music
	S_Base_StopBackgroundTrack();

	S_Base_ClearSoundBuffer ();
}

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

continuous looping sounds are added each frame

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

static void S_Base_StopLoopingSound(int entityNum) {
	loopSounds[entityNum].active = qfalse;
//	loopSounds[entityNum].sfx = 0;
	loopSounds[entityNum].kill = qfalse;
}

/*
==================
S_Base_ClearLoopingSounds

==================
*/
static void S_Base_ClearLoopingSounds( qboolean killall ) {
	int i;
	for ( i = 0 ; i < MAX_LOOP_SOUNDS ; i++) {
		if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) {
			S_Base_StopLoopingSound(i);
		}
	}
	numLoopChannels = 0;
}

/*
==================
S_Base_AddLoopingSound

Called during entity generation for a frame
Include velocity in case I get around to doing doppler...
==================
*/
static void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
	sfx_t *sfx;

	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	if (entityNum >= MAX_LOOP_SOUNDS  ||  entityNum < 0) {
		Com_Printf(S_COLOR_YELLOW "%s() invalid entity number %d\n", __FUNCTION__, entityNum);
		return;
	}

	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
		Com_Printf( S_COLOR_YELLOW "%s() handle %i out of range\n", __FUNCTION__, sfxHandle );
		return;
	}

	sfx = &s_knownSfx[ sfxHandle ];
	//Com_Printf("S_Base_AddLoopingSound %d : %s  velocity %f\n", sfxHandle, sfx->soundName, VectorLengthSquared(velocity));

	if (sfx->inMemory == qfalse) {
		S_memoryLoad(sfx);
	}

	if ( !sfx->soundLength ) {
		Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
	}

	VectorCopy( origin, loopSounds[entityNum].origin );
	VectorCopy( velocity, loopSounds[entityNum].velocity );
	loopSounds[entityNum].active = qtrue;
	loopSounds[entityNum].kill = qtrue;
	loopSounds[entityNum].doppler = qfalse;
	loopSounds[entityNum].oldDopplerScale = 1.0;
	loopSounds[entityNum].dopplerScale = 1.0;
	loopSounds[entityNum].sfx = sfx;

	if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) {
		vec3_t	out;
		float	lena, lenb;

		//Com_Printf("distance: %f\n", Distance(loopSounds[listener_number].origin, loopSounds[entityNum].origin));
		//Com_Printf("distance: %f\n", Distance(listener_origin, loopSounds[entityNum].origin));

		loopSounds[entityNum].doppler = qtrue;
		//lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin);
		lena = DistanceSquared(listener_origin, loopSounds[entityNum].origin);
		VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out);
		//lenb = DistanceSquared(loopSounds[listener_number].origin, out);
		lenb = DistanceSquared(listener_origin, out);
		if ((loopSounds[entityNum].framenum+1) != cls.framecount) {
			loopSounds[entityNum].oldDopplerScale = 1.0;
		} else {
			loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale;
		}
		loopSounds[entityNum].dopplerScale = lenb/(lena*100);
		if (loopSounds[entityNum].dopplerScale<=1.0) {
			loopSounds[entityNum].doppler = qfalse;			// don't bother doing the math
		} else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) {
		//} else if (loopSounds[entityNum].dopplerScale > 2.0) {
			loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE;
			//loopSounds[entityNum].dopplerScale = 2.0;
		}
		//if (loopSounds[entityNum].doppler) {
			//Com_Printf("dopplerscale %f\n", loopSounds[entityNum].dopplerScale);
		//}
		//if (loopSounds[entityNum].doppler) {
			//Com_Printf("doppler set: %d\n", loopSounds[entityNum].doppler);
		//}
	}

	loopSounds[entityNum].framenum = cls.framecount;
}

/*
==================
S_Base_AddLoopingSound

Called during entity generation for a frame
Include velocity in case I get around to doing doppler...
==================
*/
static void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
	sfx_t *sfx;

	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	if (entityNum >= MAX_LOOP_SOUNDS  ||  entityNum < 0) {
		Com_Printf(S_COLOR_YELLOW "%s() invalid entity number %d\n", __FUNCTION__, entityNum);
		return;
	}

	if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
		Com_Printf( S_COLOR_YELLOW "%s() handle %i out of range\n", __FUNCTION__, sfxHandle );
		return;
	}

	sfx = &s_knownSfx[ sfxHandle ];
	//Com_Printf("S_AddRealLoopingSound %d : %s\n", sfxHandle, sfx->soundName);

	if (sfx->inMemory == qfalse) {
		S_memoryLoad(sfx);
	}

	if ( !sfx->soundLength ) {
		Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
	}
	VectorCopy( origin, loopSounds[entityNum].origin );
	VectorCopy( velocity, loopSounds[entityNum].velocity );
	loopSounds[entityNum].sfx = sfx;
	loopSounds[entityNum].active = qtrue;
	loopSounds[entityNum].kill = qfalse;
	loopSounds[entityNum].doppler = qfalse;
}



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

Spatialize all of the looping sounds.
All sounds are on the same cycle, so any duplicates can just
sum up the channel multipliers.
==================
*/

static int LoopSoundsPresent[MAX_LOOP_SOUNDS];

static void S_AddLoopSounds (void) {
	int			i, j, time;
	int			left_total, right_total, left, right;
	channel_t	*ch;
	loopSound_t	*loop, *loop2;
	int numLoopSoundsPresent;
	static int loopFrame = 0;

	numLoopSoundsPresent = 0;
	for (i = 0;  i < MAX_LOOP_SOUNDS;  i++) {
		const loopSound_t *ls;

		ls = &loopSounds[i];

		if (ls->active) {
			LoopSoundsPresent[numLoopSoundsPresent] = i;
			numLoopSoundsPresent++;
			//Com_Printf("loopSound %d:  '%s'\n", i, ls->sfx->soundName);
		}
	}

	//Com_Printf("^5numLoopSoundsPresent %d\n", numLoopSoundsPresent);

	numLoopChannels = 0;

	time = S_Milliseconds();

	loopFrame++;

	//for ( i = 0 ; i < MAX_LOOP_SOUNDS ; i++) {
	for (i = 0;  i < numLoopSoundsPresent;  i++) {
		//loop = &loopSounds[i];
		loop = &loopSounds[LoopSoundsPresent[i]];

		if ( !loop->active || loop->mergeFrame == loopFrame ) {
			continue;	// already merged into an earlier sound
		}

		if (loop->kill) {
			S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total);			// 3d
		} else {
			S_SpatializeOrigin( loop->origin, 90,  &left_total, &right_total);			// sphere
		}

		loop->sfx->lastTimeUsed = time;

		//FIXME linked list for loop sounds
#if 1
		//FIXME omg
		//for (j=(i+1); j< MAX_LOOP_SOUNDS ; j++) {
		for (j = i + 1;  j < numLoopSoundsPresent;  j++) {
			//loop2 = &loopSounds[j];
			loop2 = &loopSounds[LoopSoundsPresent[j]];

			if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) {
				continue;
			}
			loop2->mergeFrame = loopFrame;

			//Com_Printf("^5testing loop  %d %d\n", i, j);

			if (loop2->kill) {
				S_SpatializeOrigin( loop2->origin, 127, &left, &right);				// 3d
			} else {
				S_SpatializeOrigin( loop2->origin, 90,  &left, &right);				// sphere
			}

			loop2->sfx->lastTimeUsed = time;
			left_total += left;
			right_total += right;
		}
#endif

		if (left_total == 0 && right_total == 0) {
			continue;		// not audible
		}

		// allocate a channel
		if (numLoopChannels >= MAX_CHANNELS) {
			Com_Printf(S_COLOR_YELLOW "%s() max loop channels %d\n", __FUNCTION__, numLoopChannels);
			return;
		}
		ch = &loop_channels[numLoopChannels];

		if (left_total > 255) {
			left_total = 255;
		}
		if (right_total > 255) {
			right_total = 255;
		}

		ch->master_vol = 127;
		ch->leftvol = left_total;
		ch->rightvol = right_total;
		ch->thesfx = loop->sfx;
		ch->doppler = loop->doppler;
		ch->dopplerScale = loop->dopplerScale;
		ch->oldDopplerScale = loop->oldDopplerScale;
		ch->fullVolume = qfalse;
		numLoopChannels++;
	}
}

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

#if 0
/*
=================
S_ByteSwapRawSamples

If raw data has been loaded in little endien binary form, this must be done.
If raw data was calculated, as with ADPCM, this should not be called.
=================
*/
void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
	int		i;

	if ( width != 2 ) {
		return;
	}
	if ( LittleShort( 256 ) == 256 ) {
		return;
	}

	if ( s_channels == 2 ) {
		samples <<= 1;
	}
	for ( i = 0 ; i < samples ; i++ ) {
		((short *)data)[i] = LittleShort( ((short *)data)[i] );
	}
}
#endif

/*
============
S_Base_RawSamples

Music streaming
============
*/
static void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum) {
	int		i;
	int		src, dst;
	float	scale;
	int             intVolumeLeft, intVolumeRight;
	portable_samplepair_t *rawsamples;

	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) {
		return;
	}

	rawsamples = s_rawsamples[stream];

	/*
	if(s_muted->integer)
		intVolume = 0;
	else
		intVolume = 256 * volume * s_volume->value;
	*/

	if ( s_muted->integer ) {
		intVolumeLeft = intVolumeRight = 0;
	} else {
		int leftvol, rightvol;

		if ( entityNum >= 0 && entityNum < MAX_GENTITIES ) {
			// support spatialized raw streams, e.g. for VoIP
			S_SpatializeOrigin( loopSounds[ entityNum ].origin, 256, &leftvol, &rightvol );
		} else {
			leftvol = rightvol = 256;
		}

		intVolumeLeft = leftvol * volume * s_volume->value;
		intVolumeRight = rightvol * volume * s_volume->value;
	}

	// compare to s_paintedtime instead of s_soundtime otherwise the data
	// between the two will be skipped during playback
	if ( s_rawend[stream] < s_paintedtime ) {
		Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_paintedtime );
		s_rawend[stream] = s_paintedtime;
	}

	scale = (float)rate / dma.speed;

//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]);
	if (s_channels == 2 && width == 2)
	{
		if (scale == 1.0)
		{	// optimized case
			for (i=0 ; i<samples ; i++)
			{
				dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
				s_rawend[stream]++;
				rawsamples[dst].left = ((short *)data)[i*2] * intVolumeLeft;
				rawsamples[dst].right = ((short *)data)[i*2+1] * intVolumeRight;
			}
		}
		else
		{
			for (i=0 ; ; i++)
			{
				src = i*scale;
				if (src >= samples)
					break;
				dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
				s_rawend[stream]++;
				rawsamples[dst].left = ((short *)data)[src*2] * intVolumeLeft;
				rawsamples[dst].right = ((short *)data)[src*2+1] * intVolumeRight;
			}
		}
	}
	else if (s_channels == 1 && width == 2)
	{
		for (i=0 ; ; i++)
		{
			src = i*scale;
			if (src >= samples)
				break;
			dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
			s_rawend[stream]++;
			rawsamples[dst].left = ((short *)data)[src] * intVolumeLeft;
			rawsamples[dst].right = ((short *)data)[src] * intVolumeRight;
		}
	}
	else if (s_channels == 2 && width == 1)
	{
		intVolumeLeft *= 256;
		intVolumeRight *= 256;

		for (i=0 ; ; i++)
		{
			src = i*scale;
			if (src >= samples)
				break;
			dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
			s_rawend[stream]++;
			rawsamples[dst].left = ((char *)data)[src*2] * intVolumeLeft;
			rawsamples[dst].right = ((char *)data)[src*2+1] * intVolumeRight;
		}
	}
	else if (s_channels == 1 && width == 1)
	{
		intVolumeLeft *= 256;
		intVolumeRight *= 256;

		for (i=0 ; ; i++)
		{
			src = i*scale;
			if (src >= samples)
				break;
			dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
			s_rawend[stream]++;
			rawsamples[dst].left = (((byte *)data)[src]-128) * intVolumeLeft;
			rawsamples[dst].right = (((byte *)data)[src]-128) * intVolumeRight;
		}
	}

	if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) {
		Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime );
	}
}

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

/*
=====================
S_Base_UpdateEntityPosition

let the sound system know where an entity currently is
======================
*/
static void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) {
	if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) {
		Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
	}
	VectorCopy( origin, loopSounds[entityNum].origin );
}


/*
============
S_Base_Respatialize

Change the volumes of all the playing sounds for changes in their positions
============
*/
static void S_Base_Respatialize( int entityNum, const vec3_t head, const vec3_t axis[3], int inwater ) {
	int			i;
	channel_t	*ch;
	vec3_t		origin;

	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	listener_number = entityNum;
	//Com_Printf("%s()  %d\n", __FUNCTION__, entityNum);
	VectorCopy(head, listener_origin);
	VectorCopy(axis[0], listener_axis[0]);
	VectorCopy(axis[1], listener_axis[1]);
	VectorCopy(axis[2], listener_axis[2]);

	// update spatialization for dynamic sounds	
	ch = s_channels;
	for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
		if ( !ch->thesfx ) {
			continue;
		}
		// local and first person sounds will always be full volume
		if (ch->fullVolume) {
			ch->leftvol = ch->master_vol;
			ch->rightvol = ch->master_vol;
		} else {
			if (ch->fixed_origin) {
				VectorCopy( ch->origin, origin );
			} else {
				VectorCopy( loopSounds[ ch->entnum ].origin, origin );
			}

			S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol);
		}
	}

	// add loopsounds
	S_AddLoopSounds ();
}


/*
========================
S_ScanChannelStarts

Returns qtrue if any new sounds were started since the last mix
========================
*/
static qboolean S_ScanChannelStarts( void ) {
	channel_t		*ch;
	int				i;
	qboolean		newSamples;
	float scale;

	newSamples = qfalse;
	ch = s_channels;

	if (s_useTimescale->integer) {
		scale = com_timescale->value;
	} else if (s_forceScale->value > 0.0) {
		scale = s_forceScale->value;
	} else {
		scale = 1;
	}

	for (i=0; i<MAX_CHANNELS ; i++, ch++) {
		if ( !ch->thesfx ) {
			continue;
		}
		// if this channel was just started this frame,
		// set the sample count to it begins mixing
		// into the very first sample
		if ( ch->startSample == START_SAMPLE_IMMEDIATE ) {
			ch->startSample = s_paintedtime;
			newSamples = qtrue;
			continue;
		}

		// if it is completely finished by now, clear it
		if ( ch->startSample + (ceil((float)ch->thesfx->soundLength / scale)) <= s_paintedtime ) {
			//Com_Printf("freeing %p\n", ch);
			S_ChannelFree(ch);
		}
	}

	return newSamples;
}

/*
============
S_Base_Update

Called once each time through the main loop
============
*/
static void S_Base_Update( void ) {
	int			i;
	int			total;
	const channel_t	*ch;

	if ( !s_soundStarted || s_soundMuted ) {
//		Com_DPrintf ("not started or muted\n");
		return;
	}

	//
	// debugging output
	//
	if ( s_show->integer >= 2 ) {
		total = 0;
		ch = s_channels;
		for (i=0 ; i<MAX_CHANNELS; i++, ch++) {
			if (ch->thesfx && (ch->leftvol || ch->rightvol) ) {
				//Com_Printf ("%03d %d %d %s  dist %f (listener %f %f %f %d -> %d)\n", i, ch->leftvol, ch->rightvol, ch->thesfx->soundName, ch->entnum != listener_number ? (ch->fixed_origin ? Distance(listener_origin, ch->origin) : Distance(loopSounds[ch->entnum].origin, listener_origin))  :  -1.0, listener_origin[0], listener_origin[1], listener_origin[2], listener_number, ch->entnum);
				Com_Printf ("%d %d %s  dist %f %d -> %d %03d\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName, ch->entnum != listener_number ? (ch->fixed_origin ? Distance(listener_origin, ch->origin) : Distance(loopSounds[ch->entnum].origin, listener_origin))  :  -1.0, listener_number, ch->entnum, i);
				total++;
			}
		}

		if (s_show->integer == 2) {
			Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime);
		}
	}

	// add raw data from streamed samples
	if (!cl_freezeDemo->integer  ||  (cl_freezeDemo->integer  &&  !cl_freezeDemoPauseMusic->integer)) {
		S_UpdateBackgroundTrack();
	}

	// mix some sound
	S_Update_();
}

static double overf = 0.0;

static void S_GetSoundtime(void)
{
	int		samplepos;
	static	int		buffers;
	static	int		oldsamplepos;
	int blurFrames;
	double msec;

	//if (CL_VideoRecording(&afdMain)  &&  !(cl_freezeDemoPauseVideoRecording->integer  &&  cl_freezeDemo->integer))
	if (CL_VideoRecording(&afdMain))
	{
		double frameRateDivider;

		frameRateDivider = (double)cl_aviFrameRateDivider->integer;
		if (frameRateDivider < 1.0) {
			frameRateDivider = 1.0;
		}
		//FIXME why no msec check like video?
		blurFrames = Cvar_VariableIntegerValue("mme_blurFrames");
		if (blurFrames == 0  ||  blurFrames == 1) {
			//msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value );
			//s_soundtime += (int)ceil( dma.speed / cl_aviFrameRate->value );
			//msec = (int)ceil( dma.speed / cl_aviFrameRate->value );
			//msec = ceil( (float)dma.speed / (float)cl_aviFrameRate->value );
			msec = ( (double)dma.speed / ((double)cl_aviFrameRate->value * frameRateDivider) );
		} else {
			//msec = (int)ceil((1000.0f / (cl_aviFrameRate->value * (float)blurFrames)) * com_timescale->value);
			//s_soundtime += (int)ceil( dma.speed / (cl_aviFrameRate->value * (float)blurFrames));
			//msec = (int)ceil( dma.speed / (cl_aviFrameRate->value * (float)blurFrames));
			//msec = ceil( (float)dma.speed / ((float)cl_aviFrameRate->value * (float)blurFrames));
			msec = ( (double)dma.speed / ((double)cl_aviFrameRate->value * (double)blurFrames * frameRateDivider));
		}
		//overf += ceil(msec) - msec;
		overf += msec - floor(msec);
		//s_soundtime += (int)ceil(msec);
		s_soundtime += (int)floor(msec);
		if (overf > 1.0) {
			//s_soundtime -= (int)floor(overf);
			s_soundtime += (int)floor(overf);
			overf -= floor(overf);
		}
		//Com_Printf("sound msec: %lf %d  overf: %f\n", msec, (int)ceil(msec), overf);
		return;
	} else {
		if (!CL_VideoRecording(&afdMain)) {
			overf = 0.0;
		}
	}

	// 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
		//Com_Printf("swap %d\n", buffers);

		if (s_paintedtime > 0x40000000)
		{	// time to chop things off to avoid 32 bit limits
			buffers = 0;
			s_paintedtime = dma.fullsamples;
			S_Base_StopAllSounds ();
			//Com_Printf("s_paintedtime (%x) > 0x40000000\n", s_paintedtime);
		}
	}
	oldsamplepos = samplepos;

	s_soundtime = buffers*dma.fullsamples + samplepos/dma.channels;

#if 0
// check to make sure that we haven't overshot
	if (s_paintedtime < s_soundtime)
	{
		Com_DPrintf ("S_Update_ : overflow\n");
		s_paintedtime = s_soundtime;
	}
#endif

	// with sdl audio, dma.submission_chunk is always 1
	if ( dma.submission_chunk < 256 ) {
		//Com_Printf("dma.submission_chunk < 256\n");
		s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed;
	} else {
		s_paintedtime = s_soundtime + dma.submission_chunk;
	}
}


static void S_Update_(void) {
	unsigned        endtime;
	static			float	lastTime = 0.0f;
	float			ma, op;
	float			thisTime, sane;
	static			int ot = -1;

	if ( !s_soundStarted || s_soundMuted ) {
		return;
	}

	thisTime = S_Milliseconds();

	// Updates s_soundtime
	S_GetSoundtime();

	if (s_soundtime == ot) {
		return;
	}
	ot = s_soundtime;

	// clear any sound effects that end before the current time,
	// and start any new sounds
	S_ScanChannelStarts();

	sane = thisTime - lastTime;
	if (sane<11) {
		sane = 11;			// 85hz
	}

	ma = s_mixahead->value * dma.speed;
	op = s_mixPreStep->value + sane*dma.speed*0.01;

	if (op < ma) {
		ma = op;
	}

	// mix ahead of current position
	endtime = s_soundtime + ma;

	// mix to an even submission block size
	endtime = (endtime + dma.submission_chunk-1)
		& ~(dma.submission_chunk-1);

	// never mix more than the complete buffer
	if (endtime - s_soundtime > dma.fullsamples)
		endtime = s_soundtime + dma.fullsamples;


	SNDDMA_BeginPainting ();

	S_PaintChannels (endtime);

	SNDDMA_Submit ();

	lastTime = thisTime;
}



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

background music functions

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

/*
======================
S_Base_StopBackgroundTrack
======================
*/
static void S_Base_StopBackgroundTrack( void ) {
	if(!s_backgroundStream)
		return;
	S_CodecCloseStream(s_backgroundStream);
	s_backgroundStream = NULL;
	s_rawend[0] = 0;
}

/*
======================
S_OpenBackgroundStream
======================
*/
static void S_OpenBackgroundStream( const char *filename ) {
	// close the background track, but DON'T reset s_rawend
	// if restarting the same back ground track
	if(s_backgroundStream)
		{
			S_CodecCloseStream(s_backgroundStream);
			s_backgroundStream = NULL;
		}

	// Open stream
	s_backgroundStream = S_CodecOpenStream(filename);
	if(!s_backgroundStream) {
		Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", filename );
		return;
	}

	if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) {
		Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo and/or channels != 2\n", filename );
		Com_Printf(S_COLOR_YELLOW "channels %d  rate %d\n", s_backgroundStream->info.channels, s_backgroundStream->info.rate);
	}
}

/*
======================
S_Base_StartBackgroundTrack
======================
*/
static void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){
	if ( !intro ) {
		intro = "";
	}
	if ( !loop || !loop[0] ) {
		loop = intro;
	}
	//Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop );
	//Com_Printf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop );

	if(!*intro)
	{
		S_Base_StopBackgroundTrack();
		return;
	}

	Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );

	S_OpenBackgroundStream( intro );
}

/*
======================
S_UpdateBackgroundTrack
======================
*/
static void S_UpdateBackgroundTrack( void ) {
	int		bufferSamples;
	int		fileSamples;
	byte	raw[30000];		// just enough to fit in a mac stack frame
	int		fileBytes;
	int		r;

	if(!s_backgroundStream) {
		return;
	}

	// don't bother playing anything if musicvolume is 0
	if ( s_musicVolume->value <= 0 ) {
		return;
	}

	// see how many samples should be copied into the raw buffer
	if ( s_rawend[0] < s_soundtime ) {
		s_rawend[0] = s_soundtime;
	}

	while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) {
		bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime);

		// decide how much data needs to be read from the file
		fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed;

		if (!fileSamples)
			return;

		// our max buffer size
		fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels);
		if ( fileBytes > sizeof(raw) ) {
			fileBytes = sizeof(raw);
			fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
		}

		// Read
		r = S_CodecReadStream(s_backgroundStream, fileBytes, raw);
		if(r < fileBytes)
		{
			fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
		}

		if(r > 0)
		{
			// add to raw buffer
			S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate,
							  s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1);
		}
		else
		{
			// loop
			if(s_backgroundLoop[0])
			{
				S_OpenBackgroundStream( s_backgroundLoop );
				if(!s_backgroundStream)
					return;
			}
			else
			{
				S_Base_StopBackgroundTrack();
				return;
			}
		}

	}
}


/*
======================
S_FreeOldestSound
======================
*/

void S_FreeOldestSound( void ) {
	int	i, oldest, used;
	sfx_t	*sfx;
	sndBuffer	*buffer, *nbuffer;

	oldest = S_Milliseconds();
	used = 0;

	for (i=1 ; i < s_numSfx ; i++) {
		sfx = &s_knownSfx[i];
		if (sfx->inMemory && sfx->lastTimeUsed<oldest) {
			used = i;
			oldest = sfx->lastTimeUsed;
		}
	}

	sfx = &s_knownSfx[used];

	Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName);

	buffer = sfx->soundData;
	while(buffer != NULL) {
		nbuffer = buffer->next;
		SND_free(buffer);
		buffer = nbuffer;
	}
	sfx->inMemory = qfalse;
	sfx->soundData = NULL;
}

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

static void S_Base_Shutdown( void ) {
	if ( !s_soundStarted ) {
		return;
	}

	SNDDMA_Shutdown();
	SND_shutdown();

	s_soundStarted = 0;
	s_numSfx = 0;

	Cmd_RemoveCommand("s_info");
}

/*
================
S_BaseInit
================
*/
qboolean S_Base_Init( soundInterface_t *si ) {
	qboolean	r;

	if( !si ) {
		return qfalse;
	}

	s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE);
	s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE);
	s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT);
	s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT);

	r = SNDDMA_Init();

	if ( r ) {
		s_soundStarted = 1;
		s_soundMuted = 1;
//		s_numSfx = 0;

		Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);

		s_soundtime = 0;
		s_paintedtime = 0;

		S_Base_StopAllSounds( );
	} else {
		return qfalse;
	}

	si->Shutdown = S_Base_Shutdown;
	si->StartSound = S_Base_StartSound;
	si->StartLocalSound = S_Base_StartLocalSound;
	si->StartBackgroundTrack = S_Base_StartBackgroundTrack;
	si->StopBackgroundTrack = S_Base_StopBackgroundTrack;
	si->RawSamples = S_Base_RawSamples;
	si->StopAllSounds = S_Base_StopAllSounds;
	si->ClearLoopingSounds = S_Base_ClearLoopingSounds;
	si->AddLoopingSound = S_Base_AddLoopingSound;
	si->AddRealLoopingSound = S_Base_AddRealLoopingSound;
	si->StopLoopingSound = S_Base_StopLoopingSound;
	si->Respatialize = S_Base_Respatialize;
	si->UpdateEntityPosition = S_Base_UpdateEntityPosition;
	si->Update = S_Base_Update;
	si->DisableSounds = S_Base_DisableSounds;
	si->BeginRegistration = S_Base_BeginRegistration;
	si->RegisterSound = S_Base_RegisterSound;
	si->ClearSoundBuffer = S_Base_ClearSoundBuffer;
	si->SoundInfo = S_Base_SoundInfo;
	si->SoundList = S_Base_SoundList;
	si->PrintSfxFilename = S_Base_PrintSfxFilename;

#ifdef USE_VOIP
	si->StartCapture = S_Base_StartCapture;
	si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples;
	si->Capture = S_Base_Capture;
	si->StopCapture = S_Base_StopCapture;
	si->MasterGain = S_Base_MasterGain;
#endif

	return qtrue;
}
