/*
Copyright (C) 1997-2001 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_dma.c -- main control for any streaming sound output device

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

void S_Play(void);
void S_SoundList(void);
void S_Update_();
void S_StopAllSounds(void);
void S_Restart (qboolean noVideo);
void S_Music( void );
void S_StopBackgroundTrack( void );

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

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

#define		SOUND_LOOPATTENUATE	0.003f

channel_t   channels[MAX_CHANNELS];

qboolean	snd_initialized = qfalse;
int			sound_started = 0;

dma_t		dma;

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

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_LOOPSFX	128
loopsfx_t	loop_sfx[MAX_LOOPSFX];
int			num_loopsfx;

#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_musicvolume;
cvar_t		*s_testsound;
cvar_t		*s_khz;
cvar_t		*s_show;
cvar_t		*s_mixahead;
cvar_t		*s_swapstereo;
cvar_t		*s_vorbis;

mempool_t	*s_mempool;

int		s_rawend;
portable_samplepair_t	s_rawsamples[MAX_RAW_SAMPLES];

bgTrack_t	*s_bgTrack;
bgTrack_t	s_bgTrackIntro;
bgTrack_t	s_bgTrackLoop;

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

/*
================
S_SoundInfo_f
================
*/
void S_SoundInfo_f( void )
{
	if( !sound_started ) {
		Com_Printf( "S_SoundInfo_f: sound system not started\n" );
		return;
	}

    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( "0x%x dma buffer\n", dma.buffer );
}

/*
================
S_SoundRestart_f
================
*/
void S_SoundRestart_f( void ) 
{
	S_Restart ( qfalse );
}

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

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

	Cmd_AddCommand( "snd_restart", S_SoundRestart_f );

	cv = Cvar_Get( "s_initsound", "1", 0 );
	if( !cv->integer ) {
		Com_Printf( "not initializing.\n" );
	} else {
		s_volume = Cvar_Get( "s_volume", "0.7", CVAR_ARCHIVE );
		s_musicvolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE );
		s_khz = Cvar_Get( "s_khz", "22", CVAR_ARCHIVE );
		s_mixahead = Cvar_Get( "s_mixahead", "0.2", CVAR_ARCHIVE );
		s_show = Cvar_Get( "s_show", "0", CVAR_CHEAT );
		s_testsound = Cvar_Get( "s_testsound", "0", 0 );
		s_swapstereo = Cvar_Get( "s_swapstereo", "0", CVAR_ARCHIVE );
		s_vorbis = Cvar_Get( "s_vorbis", "1", CVAR_ARCHIVE );

		Cmd_AddCommand( "play", S_Play );
		Cmd_AddCommand( "music", S_Music );
		Cmd_AddCommand( "stopsound", S_StopAllSounds );
		Cmd_AddCommand( "stopmusic", S_StopBackgroundTrack );
		Cmd_AddCommand( "soundlist", S_SoundList );
		Cmd_AddCommand( "soundinfo", S_SoundInfo_f );

		if( !SNDDMA_Init() )
			return;

		SNDOGG_Init ();

		S_InitScaletable ();

		sound_started = 1;
		num_sfx = 0;
		num_loopsfx = 0;

		soundtime = 0;
		paintedtime = 0;

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

		s_mempool = Mem_AllocPool ( NULL, "Sounds" );

		S_StopAllSounds ();
	}

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


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

void S_Shutdown( void )
{
	Cmd_RemoveCommand( "snd_restart" );

	if( !sound_started )
		return;

	// free all sounds
	S_FreeSounds ();

	SNDDMA_Shutdown ();
	SNDOGG_Shutdown ();

	sound_started = 0;

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

	Mem_FreePool( &s_mempool );

	num_sfx = 0;
	num_loopsfx = 0;
}


/*
=================
S_Restart

Restart the sound subsystem so it can pick up new parameters and flush all sounds
=================
*/
void S_Restart( qboolean noVideo )
{
	S_Shutdown ();
	S_Init ();

	/*
	Restart the sound subsystem
	The cgame and game must also be forced to restart because
	handles will be invalid
	*/
	if( !noVideo )
	   VID_Restart ();
}

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

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

==================
*/
sfx_t *S_FindName (char *name, qboolean create)
{
	int		i;
	sfx_t	*sfx;

	if (!name)
		Com_Error (ERR_FATAL, "S_FindName: NULL");
	if (!name[0])
	{
		*((int *)0) = -1;
		Com_Error (ERR_FATAL, "S_FindName: empty name");
	}

	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 (!strcmp(known_sfx[i].name, name))
		{
			return &known_sfx[i];
		}

	if (!create)
		return NULL;

	// find a free sfx
	for (i=0 ; i < num_sfx ; i++)
		if (!known_sfx[i].name[0])
			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));

	return sfx;
}

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

==================
*/
sfx_t *S_RegisterSound (char *name)
{
	sfx_t	*sfx;
	static char	wavname[MAX_QPATH];

	if (!sound_started)
		return NULL;

	//wsw jalsoundextension
	Q_strncpyz( wavname, name, sizeof(wavname) );
	COM_ReplaceExtension( wavname, ".wav", sizeof(wavname) );

	sfx = S_FindName (wavname, qtrue);
	S_LoadSound (sfx);

	return sfx;
}


/*
=====================
S_FreeSounds

=====================
*/
void S_FreeSounds (void)
{
	int		i;
	sfx_t	*sfx;

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

	S_StopBackgroundTrack ();
}

/*
=====================
S_SoundsInMemory

=====================
*/
void S_SoundsInMemory (void)
{
	int		i;
	sfx_t	*sfx;
	int		size;

	// free any sounds not from this registration sequence
	for (i=0, sfx=known_sfx ; i < num_sfx ; i++,sfx++)
	{
		if (!sfx->name[0])
			continue;

		// make sure it is paged in
		if (sfx->cache)
		{
			size = sfx->cache->length*sfx->cache->width;
			Com_PageInMemory ((qbyte *)sfx->cache, size);
		}
	}
}

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

/*
=================
S_PickChannel
=================
*/
channel_t *S_PickChannel(int entnum, int entchannel)
{
    int			ch_idx;
    int			first_to_die;
    int			life_left;
	channel_t	*ch;

	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;
    for (ch_idx=0 ; ch_idx < MAX_CHANNELS ; ch_idx++)
    {
		if (entchannel != 0		// channel 0 never overrides
		&& channels[ch_idx].entnum == entnum
		&& channels[ch_idx].entchannel == entchannel)
		{	// always override sound from same entity
			first_to_die = ch_idx;
			break;
		}

		// don't let monster sounds override player sounds
		if (channels[ch_idx].entnum == cl.playernum+1 && entnum != cl.playernum+1 && channels[ch_idx].sfx)
			continue;

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

	if (first_to_die == -1)
		return NULL;

	ch = &channels[first_to_die];
	memset (ch, 0, sizeof(*ch));

    return ch;
}

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

Used for spatializing channels and autosounds
=================
*/
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;

	// anything coming from the view entity will always be full volume
	// wsw: mdr: disabled and done instead in cgame module, so we can check for chasecam
	/*if (ch->entnum == cl.playernum+1)
	{
		ch->leftvol = ch->master_vol;
		ch->rightvol = ch->master_vol;
		return;
	} */

	if (ch->fixed_origin)
		VectorCopy (ch->origin, origin);
	else
		CL_GameModule_GetEntitySoundOrigin (ch->entnum, origin);

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


/*
=================
S_AllocPlaysound
=================
*/
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
=================
*/
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->integer)
		Com_Printf ("Issue %i\n", ps->begin);
	// pick a channel to play on
	ch = S_PickChannel(ps->entnum, ps->entchannel);
	if (!ch)
	{
		S_FreePlaysound (ps);
		return;
	}
	sc = S_LoadSound (ps->sfx);
	if (!sc)
	{
		S_FreePlaysound (ps);
		return;
	}

	// spatialize
	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);

	ch->pos = 0;
    ch->end = paintedtime + sc->length;

	// free the playsound
	S_FreePlaysound (ps);
}


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

/*
====================
S_StartSound

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
====================
*/
void S_StartSound(vec3_t origin, int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation, float timeofs)
{
	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 = qtrue;
	}
	else
		ps->fixed_origin = qfalse;

	ps->entnum = entnum;
	ps->entchannel = entchannel;
	ps->attenuation = attenuation;
	ps->volume = vol;
	ps->sfx = sfx;
#if 1
	// drift s_beginofs
	{
		unsigned int svTime = cl.serverTime;
		if( cl.curFrame )
			svTime = cl.curFrame->serverTime;
		start = svTime * 0.001 * dma.speed + s_beginofs;
		if (start < paintedtime)
		{
			start = paintedtime;
			s_beginofs = start - (svTime * 0.001 * dma.speed);
		}
		else if (start > paintedtime + 0.3 * dma.speed)
		{
			start = paintedtime + 0.1 * dma.speed;
			s_beginofs = start - (svTime * 0.001 * dma.speed);
		}
		else
		{
			s_beginofs -= 10;
		}
	}
#else
	// drift s_beginofs
	//start = cl.frame.serverTime * 0.001 * dma.speed + s_beginofs;
	start = cl.serverTime * 0.001 * dma.speed + s_beginofs;
	if (start < paintedtime)
	{
		start = paintedtime;
		//s_beginofs = start - (cl.frame.serverTime * 0.001 * dma.speed);
		s_beginofs = start - (cl.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);
		s_beginofs = start - (cl.serverTime * 0.001 * dma.speed);
	}
	else
	{
		s_beginofs -= 10;
	}
#endif

	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 (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
==================
*/
void S_ClearBuffer (void)
{
	int		clear;
		
	if (!sound_started)
		return;

	s_rawend = 0;

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

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

/*
==================
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));

	S_ClearBuffer ();
	S_StopBackgroundTrack ();
}

/*
==================
S_AddLoopSound
==================
*/
void S_AddLoopSound (sfx_t *sfx, vec3_t origin, float fvol, qboolean attenuate)
{
	if (!sfx || num_loopsfx >= MAX_LOOPSFX)
		return;

	loop_sfx[num_loopsfx].sfx = sfx;
	VectorCopy (origin, loop_sfx[num_loopsfx].origin);
	loop_sfx[num_loopsfx].volume = 255.0*fvol;
	loop_sfx[num_loopsfx].attenuate = attenuate;
	num_loopsfx++;
}

/*
==================
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
==================
*/
void S_AddLoopSounds (void)
{
	int			i, j;
	int			left, right, left_total, right_total;
	channel_t	*ch;
	sfx_t		*sfx;
	sfxcache_t	*sc;

	if (cls.state != CA_ACTIVE || !cl.soundPrepped)
	{
		num_loopsfx = 0;
		return;
	}

	for (i=0 ; i<num_loopsfx ; i++)
	{
		if (!loop_sfx[i].sfx)
			continue;

		sfx = loop_sfx[i].sfx;
		sc = sfx->cache;
		if (!sc)
			continue;

		// find the total contribution of all sounds of this type
		if( loop_sfx[i].attenuate ) {
			S_SpatializeOrigin (loop_sfx[i].origin, loop_sfx[i].volume, SOUND_LOOPATTENUATE,
				&left_total, &right_total);
			for (j=i+1 ; j<num_loopsfx ; j++)
			{
				if (loop_sfx[j].sfx != loop_sfx[i].sfx)
					continue;
				loop_sfx[j].sfx = NULL;	// don't check this again later

				S_SpatializeOrigin (loop_sfx[j].origin, loop_sfx[i].volume, SOUND_LOOPATTENUATE, 
					&left, &right);
				left_total += left;
				right_total += right;
			}

			if (left_total == 0 && right_total == 0)
				continue;		// not audible
		} else {
			left_total = loop_sfx[i].volume;
			right_total = loop_sfx[i].volume;
		}

		// allocate a channel
		ch = S_PickChannel(0, 0);
		if (!ch)
			return;

		if (left_total > 255)
			left_total = 255;
		if (right_total > 255)
			right_total = 255;
		ch->leftvol = left_total;
		ch->rightvol = right_total;
		ch->autosound = qtrue;	// remove next frame
		ch->sfx = sfx;
		ch->pos = paintedtime % sc->length;
		ch->end = paintedtime + sc->length - ch->pos;
	}

	num_loopsfx = 0;
}

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

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

Cinematic streaming and voice over network
============
*/
void S_RawSamples (int samples, int rate, int width, int channels, qbyte *data, qboolean swap, qboolean music)
{
	int			snd_vol;
	unsigned	src, dst;
	unsigned	fracstep, samplefrac;

	if( !sound_started )
		return;

	snd_vol = (int)((music ? s_musicvolume->value : s_volume->value) * 256);
	if( snd_vol < 0 )
		snd_vol = 0;

	if( s_rawend < paintedtime )
		s_rawend = paintedtime;

	fracstep = ((unsigned)rate << 8) / (unsigned)dma.speed;
	samplefrac = 0;

	if( width == 2 ) {
		short *in = (short *)data;

		// we never swap bytes on little endian machines so don't even bother compiling this block to reduce codesize
#ifndef ENDIAN_LITTLE
		if( swap ) {
			if( channels == 2 ) {
				for( src = 0; src < (unsigned)samples; samplefrac += fracstep, src = (samplefrac >> 8) ) {
					dst = s_rawend++ & (MAX_RAW_SAMPLES - 1);
					s_rawsamples[dst].left = LittleShort( in[src*2] ) * snd_vol;
					s_rawsamples[dst].right = LittleShort( in[src*2+1] ) * snd_vol;
				}
			} else  {
				for( src = 0; src < (unsigned)samples; samplefrac += fracstep, src = (samplefrac >> 8) ) {
					dst = s_rawend++ & (MAX_RAW_SAMPLES - 1);
					s_rawsamples[dst].left = s_rawsamples[dst].right = LittleShort( in[src] ) * snd_vol;
				}
			}
			return;
		}
#else
		swap = qfalse;		// shut up stupid compilers
#endif

		if( channels == 2 ) {
			for( src = 0; src < (unsigned)samples; samplefrac += fracstep, src = (samplefrac >> 8) ) {
				dst = s_rawend++ & (MAX_RAW_SAMPLES - 1);
				s_rawsamples[dst].left = in[src*2] * snd_vol;
				s_rawsamples[dst].right = in[src*2+1] * snd_vol;
			}
		} else  {
			for( src = 0; src < (unsigned)samples; samplefrac += fracstep, src = (samplefrac >> 8) ) {
				dst = s_rawend++ & (MAX_RAW_SAMPLES - 1);
				s_rawsamples[dst].left = s_rawsamples[dst].right = in[src] * snd_vol;
			}
		}
	} else {
		if( channels == 2 ) {
			char *in = (char *)data;

			for( src = 0; src < (unsigned)samples; samplefrac += fracstep, src = (samplefrac >> 8) ) {
				dst = s_rawend++ & (MAX_RAW_SAMPLES - 1);
				s_rawsamples[dst].left = in[src*2] << 8 * snd_vol;
				s_rawsamples[dst].right = in[src*2+1] << 8 * snd_vol;
			}
		} else {
			for( src = 0; src < (unsigned)samples; samplefrac += fracstep, src = (samplefrac >> 8) ) {
				dst = s_rawend++ & (MAX_RAW_SAMPLES - 1);
				s_rawsamples[dst].left = s_rawsamples[dst].right = (data[src] - 128) << 8 * snd_vol;
			}
		}
	}
}

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

/*
============
S_BackgroundTrack_FindNextChunk
============
*/
qboolean S_BackgroundTrack_FindNextChunk( char *name, int *last_chunk, int file )
{
	char chunkName[4];
	int iff_chunk_len;

	while( 1 ) {
		FS_Seek( file, *last_chunk, FS_SEEK_SET );

		if( FS_Eof( file ) )
			return qfalse;	// didn't find the chunk

		FS_Seek( file, 4, FS_SEEK_CUR );
		FS_Read( &iff_chunk_len, sizeof( iff_chunk_len ), file );
		iff_chunk_len = LittleLong( iff_chunk_len );
		if( iff_chunk_len < 0 )
			return qfalse;	// didn't find the chunk

		FS_Seek( file, -8, FS_SEEK_CUR );
		*last_chunk = FS_Tell( file ) + 8 + ( (iff_chunk_len + 1) & ~1 );
		FS_Read( chunkName, 4, file );
		if( !strncmp( chunkName, name, 4 ) )
			return qtrue;
	}
}

/*
============
S_BackgroundTrack_GetWavinfo
============
*/
int S_BackgroundTrack_GetWavinfo( char *name, wavinfo_t *info )
{
	short	t;
	int		samples, file;
	int 	iff_data, last_chunk;
	char	chunkName[4];

	last_chunk = 0;
	memset( info, 0, sizeof( wavinfo_t ) );

	if( FS_FOpenFile( name, &file, FS_READ ) == -1 )
		return 0;

	// find "RIFF" chunk
	if( !S_BackgroundTrack_FindNextChunk( "RIFF", &last_chunk, file ) ) {
		Com_Printf( "Missing RIFF chunk\n" );
		return 0;
	}

	FS_Read( chunkName, 4, file );
	if( !strncmp( chunkName, "WAVE", 4 ) ) {
		Com_Printf( "Missing WAVE chunk\n" );
		return 0;
	}

	// get "fmt " chunk
	iff_data = FS_Tell( file ) + 4;
	last_chunk = iff_data;
	if( !S_BackgroundTrack_FindNextChunk( "fmt ", &last_chunk, file ) ) {
		Com_Printf( "Missing fmt chunk\n" );
		return 0;
	}

	FS_Read( chunkName, 4, file );

	FS_Read( &t, sizeof( t ), file );
	if( LittleShort( t ) != 1 ) {
		Com_Printf("Microsoft PCM format only\n");
		return 0;
	}

	FS_Read( &t, sizeof( t ), file );
	info->channels = LittleShort( t );

	FS_Read( &info->rate, sizeof( info->rate ), file );
	info->rate = LittleLong( info->rate );

	FS_Seek( file, 4 + 2, FS_SEEK_CUR );

	FS_Read( &t, sizeof( t ), file );
	info->width = LittleShort( t ) / 8;

	info->loopstart = 0;

	// find data chunk
	last_chunk = iff_data;
	if( !S_BackgroundTrack_FindNextChunk( "data", &last_chunk, file ) ) {
		Com_Printf( "Missing data chunk\n" );
		return 0;
	}

	FS_Read( &samples, sizeof( samples ), file );
	info->samples = LittleLong( samples ) / info->width / info->channels;

	info->dataofs = FS_Tell( file );

	return file;
}

/*
============
S_StartBackgroundTrack
============
*/
void S_StartBackgroundTrack( char *intro, char *loop )
{
	if( !sound_started )
		return;

	S_StopBackgroundTrack ();

	if( !intro || !intro[0] )
		return;

	if( !SNDOGG_OpenTrack( intro, &s_bgTrackIntro ) ) {
		s_bgTrackIntro.file = S_BackgroundTrack_GetWavinfo( intro, &s_bgTrackIntro.info );

		if( !s_bgTrackIntro.file || !s_bgTrackIntro.info.samples )
			return;
	}

	if( loop && loop[0] && Q_stricmp( intro, loop ) ) {
		if( !SNDOGG_OpenTrack( loop, &s_bgTrackLoop ) )
			s_bgTrackLoop.file = S_BackgroundTrack_GetWavinfo( loop, &s_bgTrackLoop.info );
	}

	if( !s_bgTrackLoop.file || !s_bgTrackLoop.info.samples )
		s_bgTrackLoop = s_bgTrackIntro;
	s_bgTrack = &s_bgTrackIntro;
}

/*
============
S_StopBackgroundTrack
============
*/
void S_StopBackgroundTrack( void )
{
	if( !sound_started || !s_bgTrack )
		return;

	if( s_bgTrackIntro.file != s_bgTrackLoop.file ) {
		if( s_bgTrackIntro.close )
			s_bgTrackIntro.close( &s_bgTrackIntro );
		else
			FS_FCloseFile( s_bgTrackIntro.file );
	}

	if( s_bgTrackLoop.close )
		s_bgTrackLoop.close( &s_bgTrackLoop );
	else
		FS_FCloseFile( s_bgTrackLoop.file );

	s_bgTrack = NULL;
	memset( &s_bgTrackIntro, 0, sizeof( bgTrack_t ) );
	memset( &s_bgTrackLoop, 0, sizeof( bgTrack_t ) );
}

/*
============
S_Music
============
*/
void S_Music( void )
{
	if( Cmd_Argc () < 2 ) {
		Com_Printf( "music: <introfile> [loopfile]\n" );
		return;
	}

	S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ) );
}

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

/*
============
S_UpdateBackgroundTrack
============
*/
void S_UpdateBackgroundTrack( void )
{
	int		samples, maxSamples;
	int		read, maxRead, total;
	float	scale;
	qbyte	data[MAX_RAW_SAMPLES*4];
#if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
	qbyte	swaptest[2] = {1, 0};
#endif
	
	if( !s_bgTrack || !s_musicvolume->value )
		return;

	if( s_rawend < paintedtime )
		s_rawend = paintedtime;
	scale = (float)s_bgTrack->info.rate / dma.speed;
	maxSamples = sizeof( data ) / s_bgTrack->info.channels / s_bgTrack->info.width;

	while( 1 ) {
		samples = ( paintedtime + MAX_RAW_SAMPLES - s_rawend ) * scale;
		if( samples <= 0 )
			return;
		if( samples > maxSamples )
			samples = maxSamples;
		maxRead = samples * s_bgTrack->info.channels * s_bgTrack->info.width;

		total = 0;
		while( total < maxRead ) {
			if( s_bgTrack->read )
				read = s_bgTrack->read( s_bgTrack, data + total, maxRead - total );
			else
				read = FS_Read( data + total, maxRead - total, s_bgTrack->file );

			if( !read ){
				if( s_bgTrackIntro.file != s_bgTrackLoop.file ) {
					if( s_bgTrackIntro.close )
						s_bgTrackIntro.close( &s_bgTrackIntro );
					else
						FS_FCloseFile( s_bgTrackIntro.file );
					s_bgTrackIntro = s_bgTrackLoop;
				}

				s_bgTrack = &s_bgTrackLoop;
				if( s_bgTrack->seek )
					s_bgTrack->seek( s_bgTrack, s_bgTrack->info.dataofs );
				else
					FS_Seek( s_bgTrack->file, s_bgTrack->info.dataofs, FS_SEEK_SET );
			}

			total += read;
		}

#ifdef ENDIAN_LITTLE
		S_RawSamples( samples, s_bgTrack->info.rate, s_bgTrack->info.width, s_bgTrack->info.channels, data, qfalse, qtrue );
#elif defined(ENDIAN_BIG)
		S_RawSamples( samples, s_bgTrack->info.rate, s_bgTrack->info.width, s_bgTrack->info.channels, data, qtrue, qtrue );
#elif !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
		S_RawSamples( samples, s_bgTrack->info.rate, s_bgTrack->info.width, s_bgTrack->info.channels, data, (*(short *)swaptest == 0), qtrue );
#endif
	}
}

/*
============
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 loading 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
	if (s_volume->modified)
		S_InitScaletable ();

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

	combine = NULL;

	// update spatialization for dynamic sounds	
	ch = channels;
	for (i=0 ; i<MAX_CHANNELS; i++, ch++)
	{
		if (!ch->sfx)
			continue;
		if (ch->autosound)
		{	// autosounds are regenerated fresh each frame
			memset (ch, 0, sizeof(*ch));
			continue;
		}
		S_Spatialize(ch);         // respatialize channel
		if (!ch->leftvol && !ch->rightvol)
		{
			memset (ch, 0, sizeof(*ch));
			continue;
		}
	}

	// add loopsounds
	S_AddLoopSounds ();

	//
	// debugging output
	//
	if (s_show->integer)
	{
		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_UpdateBackgroundTrack ();

	S_Update_();
}

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;
}


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

	if (!sound_started)
		return;

	SNDDMA_BeginPainting ();

	if (!dma.buffer)
		return;

// Updates DMA time
	GetSoundtime();

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

// mix ahead of current position
	endtime = soundtime + s_mixahead->value * dma.speed;

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

	S_PaintChannels (endtime);

	SNDDMA_Submit ();
}

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

console functions

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

void S_Play(void)
{
	int 	i;
	char name[256];
	sfx_t	*sfx;
	
	i = 1;
	while (i<Cmd_Argc())
	{
		if (!strrchr(Cmd_Argv(i), '.'))
		{
			Q_strncpyz(name, Cmd_Argv(i), sizeof(name));
			Q_strncatz(name, ".wav", sizeof(name));
		}
		else
			Q_strncpyz(name, Cmd_Argv(i), sizeof(name));
		sfx = S_RegisterSound(name);
		S_StartSound(NULL, cl.playernum+1, 0, sfx, 1.0, 1.0, 0);
		i++;
	}
}

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++)
	{
		if (!sfx->name[0])
			continue;
		sc = sfx->cache;
		if (sc)
		{
			size = sc->length*sc->width*sc->channels;
			total += size;
			if (sc->loopstart >= 0)
				Com_Printf ("L");
			else
				Com_Printf (" ");
			Com_Printf ("(%2db) %6i : %s\n",sc->width*8,  size, sfx->name);
		}
		else
		{
			if (sfx->name[0] == '*')
				Com_Printf ("  placeholder : %s\n", sfx->name);
			else
				Com_Printf ("  not loaded  : %s\n", sfx->name);
		}
	}
	Com_Printf ("Total resident: %i\n", total);
}
