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

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

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

See the GNU General Public License for more details.

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

*/
// snd_dma.c -- main control for any streaming sound output devices

#include "quakedef.h"

#ifndef __CYGWIN__

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

void S_Play(void);
void S_PlayVol(void);
void S_SoundList(void);
void S_Update_(soundcardinfo_t *sc);
void S_StopAllSounds(qboolean clear);
void S_StopAllSoundsC(void);

void S_UpdateCard(soundcardinfo_t *sc);

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


int				snd_blocked = 0;
static qboolean	snd_ambient = 1;
qboolean		snd_initialized = false;
int				snd_speed;

qboolean snd_multipledevices = false;

vec3_t		listener_origin;
vec3_t		listener_forward;
vec3_t		listener_right;
vec3_t		listener_up;
vec_t		sound_nominal_clip_dist=1000.0;

int			soundtime;		// sample PAIRS


#define	MAX_SFX		512
sfx_t		*known_sfx;		// hunk allocated [MAX_SFX]
int			num_sfx;

sfx_t		*ambient_sfx[NUM_AMBIENTS];

//int 		desired_speed = 44100;
int 		desired_bits = 16;

int sound_started=0;

cvar_t bgmvolume = {"musicvolume", "0", NULL, CVAR_ARCHIVE};
cvar_t volume = {"volume", "0.7", NULL, CVAR_ARCHIVE};

cvar_t nosound = {"nosound", "0"};
cvar_t precache = {"precache", "1"};
cvar_t loadas8bit = {"loadas8bit", "0"};
cvar_t bgmbuffer = {"bgmbuffer", "4096"};
cvar_t ambient_level = {"ambient_level", "0.3"};
cvar_t ambient_fade = {"ambient_fade", "100"};
cvar_t snd_noextraupdate = {"snd_noextraupdate", "0"};
cvar_t snd_show = {"snd_show", "0"};
cvar_t snd_khz = {"snd_khz", "11"};
cvar_t	snd_inactive = {"snd_inactive", "0"};	//set if you want sound even when tabbed out.
cvar_t _snd_mixahead = {"_snd_mixahead", "0.2", NULL, CVAR_ARCHIVE};
cvar_t snd_leftisright = {"snd_leftisright", "0", NULL, CVAR_ARCHIVE};
cvar_t snd_eax = {"snd_eax", "0"};
cvar_t snd_speakers = {"snd_numspeakers", "2"};
cvar_t	snd_playersoundvolume = {"snd_localvolume", "1"};	//sugested by crunch

cvar_t	snd_capture = {"snd_capture", "0"};


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


//
// Fake dma is a synchronous faking of the DMA progress used for
// isolating performance in the renderer.  The fakedma_updates is
// number of times S_Update() is called per second.
//

qboolean fakedma = false;
int fakedma_updates = 15;


void S_AmbientOff (void)
{
	snd_ambient = false;
}


void S_AmbientOn (void)
{
	snd_ambient = true;
}


void S_SoundInfo_f(void)
{
	soundcardinfo_t *sc;
	if (!sound_started)
	{
		Con_Printf ("sound system not started\n");
		return;
	}

	if (!sndcardinfo)
	{
		Con_Print("No sound cards\n");
		return;
	}
	for (sc = sndcardinfo; sc; sc = sc->next)
	{
		Con_Printf("%5d stereo\n", sc->sn.numchannels - 1);
		Con_Printf("%5d samples\n", sc->sn.samples);
		Con_Printf("%5d samplepos\n", sc->sn.samplepos);
		Con_Printf("%5d samplebits\n", sc->sn.samplebits);
		Con_Printf("%5d submission_chunk\n", sc->sn.submission_chunk);
		Con_Printf("%5d speed\n", sc->sn.speed);
		Con_Printf("0x%x dma buffer\n", sc->sn.buffer);
		Con_Printf("%5d total_channels\n", sc->total_chans);
	}
}


/*
================
S_Startup
================
*/

void S_Startup (void)
{
	int warningmessage=0;
	int		rc;
	soundcardinfo_t *sc;

	if (!snd_initialized)
		return;

	if (sound_started)
		S_Shutdown();

	snd_blocked = 0;

	if (!fakedma)
	{
#if defined(_WIN32) && !defined(NODIRECTX)
		extern int aimedforguid;	//restart directsound guid aims.
		aimedforguid=0;
#endif
		for(;;)
		{
			sc = Z_Malloc(sizeof(soundcardinfo_t));
			if (snd_khz.value >= 45)
				sc->sn.speed = 48000;
			else if (snd_khz.value >= 30)	//set by a slider
				sc->sn.speed = 44100;
			else if (snd_khz.value >= 20)
				sc->sn.speed = 22050;
			else
				sc->sn.speed = 11025;
			rc = SNDDMA_Init(sc);

			if (!rc)	//error stop
			{
#ifndef	_WIN32
				Con_Printf("S_Startup: SNDDMA_Init failed.\n");
#endif
				Z_Free(sc);
				break;
			}
			if (rc == 2)	//silently stop (no more cards)
			{
				Z_Free(sc);
				break;
			}

			if (sc->sn.numchannels < 3)
			{
				sc->pitch[0] = 0;
				sc->pitch[1] = 0;
				sc->dist[0] = 1;
				sc->dist[1] = 1;
				sc->yaw[0] = 270;
				sc->yaw[1] = 90;
			}
			else if (sc->sn.numchannels < 5)
			{
				sc->pitch[0] = 0;
				sc->pitch[1] = 0;
				sc->pitch[2] = 0;
				sc->pitch[3] = 0;
				sc->dist[0] = 1;
				sc->dist[1] = 1;
				sc->dist[2] = 1;
				sc->dist[3] = 1;
				sc->yaw[0] = 315;
				sc->yaw[1] = 45;
				sc->yaw[2] = 225;
				sc->yaw[3] = 135;
			}
			else
			{
				sc->pitch[0] = 0;
				sc->pitch[1] = 0;
				sc->pitch[2] = 0;
				sc->pitch[3] = 0;
				sc->pitch[4] = 0;
				sc->pitch[5] = 0;
				sc->dist[0] = 1;
				sc->dist[1] = 1;
				sc->dist[2] = 1;
				sc->dist[3] = 1;
				sc->dist[4] = 1;
				sc->dist[5] = 1;
				sc->yaw[0] = 315;
				sc->yaw[1] = 45;
				sc->yaw[2] = 0;
				sc->yaw[3] = 0;
				sc->yaw[4] = 225;
				sc->yaw[5] = 135;
			}

			if (sndcardinfo)
			{	//if the sample speeds of multiple soundcards do not match, it'll fail.
				if (sc->sn.speed != sc->sn.speed)
				{
					if (!warningmessage)
					{
						Con_Printf("S_Startup: Ignoring soundcard %s due to mismatched sample speeds.\nTry running Quake with -singlesound to use just the primary soundcard\n", sc->name);
						S_ShutdownCard(sc);
						warningmessage = true;
					}

					Z_Free(sc);					
					continue;
				}
			}
			else
				snd_speed = sc->sn.speed;

			sc->next = sndcardinfo;
			sndcardinfo = sc;
		}
	}

	sound_started = 1;
}

void S_Restart_f (void)
{	
	extern qboolean snd_firsttime;
	Cache_Flush();//forget the old sounds.

	if (COM_CheckParm("-nosound"))
		return;

	S_StopAllSounds (true);	

	S_Shutdown();
	sound_started = 0;
	snd_firsttime = true;
	S_Startup();

	ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav");
	ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav");	

	S_StopAllSounds (true);
}

void S_Control_f (void)
{	
	int i;
	char *command;
	if (Cmd_Argc() < 2)
		return;

	command = Cmd_Argv (1);

	if (!Q_strcasecmp(command, "off"))
	{
		Cache_Flush();//forget the old sounds.

		S_StopAllSounds (true);	

		S_Shutdown();
		sound_started = 0;
	}
	else if (!Q_strcasecmp(command, "multi") || !Q_strcasecmp(command, "multiple"))
	{
		if (!Q_strcasecmp(Cmd_Argv (2), "off"))
		{
			if (snd_multipledevices)
			{
				snd_multipledevices = false;
				S_Restart_f();				
			}			
		}
		else if (!snd_multipledevices)
		{
			snd_multipledevices = true;
			S_Restart_f();
		}
		return;
	}
	if (!Q_strcasecmp(command, "single"))
	{
		snd_multipledevices = false;
		S_Restart_f();
		return;
	}

	if (!Q_strcasecmp(command, "rate") || !Q_strcasecmp(command, "speed"))
	{
		Cvar_SetValue(&snd_khz, atof(Cmd_Argv (2))/1000);		
		S_Restart_f();
		return;
	}

	//individual device control
	if (!Q_strncasecmp(command, "card", 4))
	{
		int card;
		soundcardinfo_t *sc;
		card = atoi(command+4);

		for (i = 0, sc = sndcardinfo; i < card && sc; i++,sc=sc->next)
			;

		if (!sc)
		{
			Con_Printf("Sound card %i is invalid (try resetting first)\n", card);
			return;
		}
		if (Cmd_Argc() < 3)
		{
			Con_Printf("Scard %i is %s\n", card, sc->name);
			return;
		}
		command = Cmd_Argv (2);
		if (!Q_strcasecmp(command, "mono"))
		{
			for (i = 0; i < MAXSOUNDCHANNELS; i++)
			{
				sc->yaw[i] = 0;
				sc->pitch[i] = 0;	//point forwards
				sc->dist[i] = 1;
			}
		}
		else if (!Q_strcasecmp(command, "standard") || !Q_strcasecmp(command, "stereo"))
		{
			for (i = 0; i < MAXSOUNDCHANNELS; i++)
			{
				if (i & 1)
					sc->yaw[i] = 90;	//right
				else
					sc->yaw[i] = 270;	//left
				sc->pitch[i] = 0;
				sc->dist[i] = 1;
			}
		}
		else if (!Q_strcasecmp(command, "swap"))
		{
			for (i = 0; i < MAXSOUNDCHANNELS; i++)
			{
				if (i & 1)
					sc->yaw[i] = 270;	//left
				else
					sc->yaw[i] = 90;	//right
				sc->pitch[i] = 0;
				sc->dist[i] = 1;
			}
		}
		else if (!Q_strcasecmp(command, "front"))
		{
			for (i = 0; i < MAXSOUNDCHANNELS; i++)
			{
				if (i & 1)
					sc->yaw[i] = 45;	//front right
				else
					sc->yaw[i] = 315;	//front left
				sc->pitch[i] = 0;
				sc->dist[i] = 1;
			}
		}
		else if (!Q_strcasecmp(command, "back"))
		{
			for (i = 0; i < MAXSOUNDCHANNELS; i++)
			{
				if (i & 1)
					sc->yaw[i] = 180-45;	//behind right
				else
					sc->yaw[i] = 180+45;	//behind left
				sc->pitch[i] = 0;
				sc->dist[i] = 1;
			}
		}
		return;
	}
}

/*
================
S_Init
================
*/
void S_Init (void)
{
	int p;

	if (snd_initialized)	//whoops
	{
		Con_Printf("Sound is already initialized\n");
		return;
	}
	Con_DPrintf("\nSound Initialization\n");

	if (COM_CheckParm("-nosound"))
	{
		Cvar_Register(&nosound,				"Sound controls");
		Cvar_ForceSet(&nosound, "1");
		nosound.flags |= CVAR_NOSET;
		return;
	}

//	if (COM_CheckParm("-simsound"))
//		fakedma = true;

	if (COM_CheckParm ("-nomultipledevices") || COM_CheckParm ("-singlesound"))
		snd_multipledevices = false;

	if (COM_CheckParm ("-multisound"))
		snd_multipledevices = true;

	p = COM_CheckParm ("-soundspeed");
	if (p)
	{
		if (p < com_argc-1)
			Cvar_SetValue(&snd_khz, atof(com_argv[p+1])/1000);
		else
			Sys_Error ("S_Init: you must specify a speed in KB after -soundspeed");
	}		


	Cmd_AddCommand("play", S_Play);
	Cmd_AddCommand("playvol", S_PlayVol);
	Cmd_AddCommand("stopsound", S_StopAllSoundsC);
	Cmd_AddCommand("soundlist", S_SoundList);
	Cmd_AddCommand("soundinfo", S_SoundInfo_f);

	Cmd_AddCommand("snd_restart", S_Restart_f);

	Cmd_AddCommand("soundcontrol", S_Control_f);

	Cvar_Register(&nosound,				"Sound controls");
	Cvar_Register(&volume,				"Sound controls");
	Cvar_Register(&precache,			"Sound controls");
	Cvar_Register(&loadas8bit,			"Sound controls");
	Cvar_Register(&bgmvolume,			"Sound controls");
	Cvar_Register(&bgmbuffer,			"Sound controls");
	Cvar_Register(&ambient_level,		"Sound controls");
	Cvar_Register(&ambient_fade,		"Sound controls");
	Cvar_Register(&snd_noextraupdate,	"Sound controls");
	Cvar_Register(&snd_show,			"Sound controls");
	Cvar_Register(&_snd_mixahead,		"Sound controls");
	Cvar_Register(&snd_khz,				"Sound controls");
	Cvar_Register(&snd_leftisright,		"Sound controls");
	Cvar_Register(&snd_eax,				"Sound controls");
	Cvar_Register(&snd_speakers,		"Sound controls");

	Cvar_Register(&snd_capture,			"Sound controls");

	Cvar_Register(&snd_inactive,		"Sound controls");

	Cvar_Register(&snd_playersoundvolume,		"Sound controls");

	if (host_parms.memsize < 0x800000)
	{
		Cvar_Set (&loadas8bit, "1");
		Con_Printf ("loading all sounds as 8bit\n");
	}



	snd_initialized = true;

	SND_InitScaletable ();

	known_sfx = Hunk_AllocName (MAX_SFX*sizeof(sfx_t), "sfx_t");
	num_sfx = 0;

// create a piece of DMA memory

/*	if (fakedma)
	{
		cursndcard = Z_Malloc(sizeof(*sndcardinfo));
		cursndcard->next = sndcardinfo;
		sndcardinfo = cursndcard;

		shm = (void *) Hunk_AllocName(sizeof(*shm), "shm");
		shm->splitbuffer = 0;
		shm->samplebits = 16;
		shm->speed = 22050;
		shm->numchannels = 2;
		shm->samples = 44100;
		shm->samplepos = 0;
		shm->soundalive = true;
		shm->gamealive = true;
		shm->submission_chunk = 1;
		shm->buffer = Hunk_AllocName(1<<16, "shmbuf");
	}
*/
	if (sndcardinfo)
		Con_SafePrintf ("Sound sampling rate: %i\n", sndcardinfo->sn.speed);

	// provides a tick sound until washed clean

//	if (shm->buffer)
//		shm->buffer[4] = shm->buffer[5] = 0x7f;	// force a pop for debugging

	ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav");
	ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav");

	S_StopAllSounds (true);
}


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

void S_ShutdownCard(soundcardinfo_t *sc)
{
#if defined(_WIN32) && !defined(NODIRECTX)
	extern int aimedforguid;
	aimedforguid = 0;
#endif
	if (!fakedma)
	{		
		SNDDMA_Shutdown(sc);
	}	
}
void S_Shutdown(void)
{	
	soundcardinfo_t *sc, *next;
	for (sc = sndcardinfo; sc; sc=next)
	{
		next = sc->next;
		S_ShutdownCard(sc);
		Z_Free(sc);
		sndcardinfo = next;
	}

	sound_started = 0;
}


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

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

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

	if (!name)
		Sys_Error ("S_FindName: NULL\n");

	if (Q_strlen(name) >= MAX_OSPATH)
		Sys_Error ("Sound name too long: %s", name);

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

	if (num_sfx == MAX_SFX)
		Sys_Error ("S_FindName: out of sfx_t");
	
	sfx = &known_sfx[i];
	strcpy (sfx->name, name);

	num_sfx++;
	
	return sfx;
}


/*
==================
S_TouchSound

==================
*/
void S_TouchSound (char *name)
{
	sfx_t	*sfx;
	
	if (!sound_started)
		return;

	sfx = S_FindName (name);
	Cache_Check (&sfx->cache);
}

/*
==================
S_PrecacheSound

==================
*/
sfx_t *S_PrecacheSound (char *name)
{
	sfx_t	*sfx;

	if (nosound.value)
		return NULL;

	sfx = S_FindName (name);
	
// cache it in
	if (precache.value &&sndcardinfo)
		S_LoadSound (sfx);
	
	return sfx;
}


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

/*
=================
SND_PickChannel
=================
*/
channel_t *SND_PickChannel(soundcardinfo_t *sc, int entnum, int entchannel)
{
    int ch_idx;
    int first_to_die;
    int life_left;

// Check for replacement sound, or find the best one to replace
    first_to_die = -1;
    life_left = 0x7fffffff;
    for (ch_idx=NUM_AMBIENTS + NUM_MUSICS; ch_idx < NUM_AMBIENTS + NUM_MUSICS + MAX_DYNAMIC_CHANNELS ; ch_idx++)
    {
		if (entchannel != 0		// channel 0 never overrides
		&& sc->channel[ch_idx].entnum == entnum
		&& (sc->channel[ch_idx].entchannel == entchannel || entchannel == -1) && sc->channel[ch_idx].end > snd_speed/10)
		{	// allways override sound from same entity
			first_to_die = ch_idx;
			break;
		}

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

		if (sc->channel[ch_idx].end < life_left)
		{
			life_left = sc->channel[ch_idx].end;
			first_to_die = ch_idx;
		}
   }

	if (first_to_die == -1)
		return NULL;

	if (sc->channel[first_to_die].sfx)
		sc->channel[first_to_die].sfx = NULL;

    return &sc->channel[first_to_die];    
}       

/*
=================
SND_Spatialize
=================
*/
void SND_Spatialize(soundcardinfo_t *sc, channel_t *ch)
{
    vec_t dotright, dotforward, dotup;
    vec_t dist;
    vec_t scale;
    vec3_t source_vec;
	sfx_t *snd;
	int i;

// anything coming from the view entity will allways be full volume
	if (ch->entnum == -1 || ch->entnum == cl.playernum[0]+1)
	{
		for (i = 0; i < sc->sn.numchannels; i++)
		{
			ch->vol[i] = ch->master_vol * snd_playersoundvolume.value;
			ch->delay[i] = 0;
		}
		return;
	}

// calculate stereo seperation and distance attenuation
	snd = ch->sfx;
	VectorSubtract(ch->origin, listener_origin, source_vec);
	
	dist = VectorNormalize(source_vec) * ch->dist_mult;
	
	dotright = DotProduct(listener_right, source_vec);
	dotforward = DotProduct(listener_forward, source_vec);
	dotup = DotProduct(listener_up, source_vec);

	for (i = 0; i < sc->sn.numchannels; i++)
	{
		scale = 1 + (dotright*sin(sc->yaw[i]*M_PI/180) + dotforward*cos(sc->yaw[i]*M_PI/180));// - dotup*cos(sc->pitch[0])*2;
		scale = (1.0 - dist) * scale * sc->dist[i];
		ch->vol[i] = (int) (ch->master_vol * scale);
		if (ch->vol[i] < 0)
			ch->vol[i] = 0;

		ch->delay[i] = 0;//(scale[0]-1)*1024;
	}
}

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

void S_StartSoundCard(soundcardinfo_t *sc, int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, int startpos)
{
	channel_t *target_chan, *check;
	sfxcache_t	*scache;
	int		vol;
	int		ch_idx;
	int		skip;

	if (!sound_started)
		return;

	if (!sfx)
		return;

	if (nosound.value)
		return;

	vol = fvol*255;

// pick a channel to play on
	target_chan = SND_PickChannel(sc, entnum, entchannel);
	if (!target_chan)
		return;
		
// spatialize
	memset (target_chan, 0, sizeof(*target_chan));
	if (!origin)
	{
		VectorCopy(listener_origin, target_chan->origin);
	}
	else
	{
		VectorCopy(origin, target_chan->origin);
	}
	target_chan->dist_mult = attenuation / sound_nominal_clip_dist;
	target_chan->master_vol = vol;
	target_chan->entnum = entnum;
	target_chan->entchannel = entchannel;
	SND_Spatialize(sc, target_chan);

	if (!target_chan->vol[0] && !target_chan->vol[1] && !target_chan->vol[2] && !target_chan->vol[3] && !target_chan->vol[4] && !target_chan->vol[5])
		return;		// not audible at all

// new channel
	scache = S_LoadSound (sfx);
	if (!scache)
	{
		target_chan->sfx = NULL;
		return;		// couldn't load the sound's data
	}
	target_chan->sfx = sfx;
	target_chan->pos = startpos;
	target_chan->end = sc->paintedtime + scache->length;
	target_chan->looping = false;
	
// if an identical sound has also been started this frame, offset the pos
// a bit to keep it from just making the first one louder
	check = &sc->channel[NUM_AMBIENTS];
	for (ch_idx=NUM_AMBIENTS + NUM_MUSICS; ch_idx < NUM_AMBIENTS + NUM_MUSICS + MAX_DYNAMIC_CHANNELS; ch_idx++, check++)
	{
		if (check == target_chan)
			continue;
		if (check->sfx == sfx && !check->pos)
		{
			skip = rand () % (int)(0.1*sc->sn.speed);
			if (skip >= target_chan->end)
				skip = target_chan->end - 1;
			target_chan->pos += skip;
			target_chan->end -= skip;
			break;
		}
	}
}

void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation)
{
	soundcardinfo_t *sc;

	if (!sfx || !*sfx->name)	//no named sounds would need specific starting.
		return;

	for (sc = sndcardinfo; sc; sc = sc->next)
		S_StartSoundCard(sc, entnum, entchannel, sfx, origin, fvol, attenuation, 0);
}

qboolean S_IsPlayingSomewhere(sfx_t *s)
{
	soundcardinfo_t *si;
	int i;
	for (si = sndcardinfo; si; si=si->next)
	{
		for (i = 0; i < MAX_CHANNELS; i++)
		if (si->channel[i].sfx == s)
			return true;
	}
	return false;
}

void S_StopSoundCard(soundcardinfo_t *sc, int entnum, int entchannel)
{
	int i;

	for (i=0 ; i<sc->total_chans ; i++)
	{
		if (sc->channel[i].entnum == entnum
			&& sc->channel[i].entchannel == entchannel)
		{
			sc->channel[i].end = 0;
			sc->channel[i].sfx = NULL;
			return;
		}
	}
}

void S_StopSound(int entnum, int entchannel)
{
	soundcardinfo_t *sc;
	for (sc = sndcardinfo; sc; sc = sc->next)
		S_StopSoundCard(sc, entnum, entchannel);
}

void S_StopAllSounds(qboolean clear)
{
	int		i;
	sfx_t *s;

	soundcardinfo_t *sc;

	for (sc = sndcardinfo; sc; sc = sc->next)
	{
		if (!sound_started)
			return;


		sc->total_chans = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS + NUM_MUSICS;	// no statics

		for (i=0 ; i<MAX_CHANNELS ; i++)
			if (sc->channel[i].sfx)
			{
				s = sc->channel[i].sfx;
				sc->channel[i].sfx = NULL;
				if (s->decoder)
				if (!S_IsPlayingSomewhere(s))	//if we aint playing it elsewhere, free it compleatly.
				{
					s->decoder->abort(s);
				}
			}

		Q_memset(sc->channel, 0, MAX_CHANNELS * sizeof(channel_t));

		if (clear)
			S_ClearBuffer (sc);
	}
}

void S_StopAllSoundsC (void)
{
	S_StopAllSounds (true);
}

void S_ClearBuffer (soundcardinfo_t *sc)
{
	int		clear;
#if defined(_WIN32) && !defined(NODIRECTX)
	if (!sound_started || (!sc->sn.buffer && !sc->pDSBuf))
#else
	if (!sound_started || !sc->sn.buffer)
#endif
		return;

	if (sc->sn.samplebits == 8)
		clear = 0x80;
	else
		clear = 0;

#if defined(_WIN32) && !defined(NODIRECTX)
	if (sc->pDSBuf)
	{
		DWORD	dwSize;
		DWORD	*pData;
		int		reps;
		HRESULT	hresult;

		reps = 0;

		while ((hresult = sc->pDSBuf->lpVtbl->Lock(sc->pDSBuf, 0, sc->gSndBufSize, (void**)&pData, &dwSize, NULL, NULL, 0)) != DS_OK)
		{
			if (hresult != DSERR_BUFFERLOST)
			{
				Con_Printf ("S_ClearBuffer: DS::Lock Sound Buffer Failed\n");
				S_ShutdownCard (sc);
				return;
			}

			if (++reps > 10000)
			{
				Con_Printf ("S_ClearBuffer: DS: couldn't restore buffer\n");
				S_ShutdownCard (sc);
				return;
			}
		}

		Q_memset(pData, clear, sc->sn.samples * sc->sn.samplebits/8);

		sc->pDSBuf->lpVtbl->Unlock(sc->pDSBuf, pData, dwSize, NULL, 0);
	
	}
	else
#endif
	{
		Q_memset(sc->sn.buffer, clear, sc->sn.samples * sc->sn.samplebits/8);
	}
}

/*
=================
S_StaticSound
=================
*/
void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation)
{
	channel_t	*ss;
	sfxcache_t		*scache;
	soundcardinfo_t *scard;

	if (!sfx)
		return;

	for (scard = sndcardinfo; scard; scard = scard->next)
	{
		if (scard->total_chans == MAX_CHANNELS)
		{
			Con_Printf ("total_channels == MAX_CHANNELS\n");
			continue;
		}

		ss = &scard->channel[scard->total_chans];
		scard->total_chans++;

		scache = S_LoadSound (sfx);
		if (!scache)
			return;

		if (scache->loopstart == -1)
		{
			Con_Printf ("Ambient sound %s not looped\n", sfx->name);
			return;
		}

		ss->sfx = sfx;
		VectorCopy (origin, ss->origin);
		ss->master_vol = vol;
		ss->dist_mult = (attenuation/64) / sound_nominal_clip_dist;
		ss->end = scard->paintedtime + scache->length;
		ss->looping = true;
		
		SND_Spatialize (scard, ss);
	}
}


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

/*
===================
S_UpdateAmbientSounds
===================
*/
char *Media_NextTrack(void);
void S_UpdateAmbientSounds (soundcardinfo_t *sc)
{
	mleaf_t		*l;
	float		vol;
	int			ambient_channel;
	channel_t	*chan;
	int i;

	if (!snd_ambient)
		return;

	for (i = NUM_AMBIENTS; i < NUM_AMBIENTS + NUM_MUSICS; i++)
	{
		chan = &sc->channel[i];
		if (!chan->sfx)
		{
			char *nexttrack = Media_NextTrack();
			sfxcache_t *scache;
			sfx_t *newmusic;

			if (nexttrack && *nexttrack)
			{
				newmusic = S_PrecacheSound(nexttrack);


				scache = S_LoadSound (newmusic);
				if (scache)
				{			
					chan->sfx = newmusic;
					chan->pos = 0;
					chan->end = sc->paintedtime + scache->length+1000;
					chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol = 100;
				}
			}
		}
		if (chan->sfx)
			chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol = (255/volume.value)*bgmvolume.value;
	}


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

	l = Mod_PointInLeaf (listener_origin, cl.worldmodel);
	if (!l || !ambient_level.value)
	{
		for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++)
			sc->channel[ambient_channel].sfx = NULL;
		return;
	}

	for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++)
	{
		chan = &sc->channel[ambient_channel];	
		chan->sfx = ambient_sfx[ambient_channel];

		VectorCopy(listener_origin, chan->origin);
	
		vol = ambient_level.value * l->ambient_sound_level[ambient_channel];
		if (vol < 8)
			vol = 0;

	// don't adjust volume too fast
		if (chan->master_vol < vol)
		{
			chan->master_vol += host_frametime * ambient_fade.value;
			if (chan->master_vol > vol)
				chan->master_vol = vol;
		}
		else if (chan->master_vol > vol)
		{
			chan->master_vol -= host_frametime * ambient_fade.value;
			if (chan->master_vol < vol)
				chan->master_vol = vol;
		}
		
		chan->vol[0] = chan->vol[1] = chan->vol[2] = chan->vol[3] = chan->vol[4] = chan->vol[5] = chan->master_vol;
	}
}

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

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

	S_UpdateCapture();

	for (sc = sndcardinfo; sc; sc = sc->next)
		S_UpdateCard(sc);

}
void S_UpdateCard(soundcardinfo_t *sc)
{
	int			i, j;
	int			total;
	channel_t	*ch;
	channel_t	*combine;

	if (!sound_started)
		return;
	if ((snd_blocked > 0))
	{
		if (!sc->inactive_sound)
			return;
	}
	
// update general area ambient sound sources
	S_UpdateAmbientSounds (sc);

	combine = NULL;

// update spatialization for static and dynamic sounds	
	ch = sc->channel+NUM_AMBIENTS+NUM_MUSICS;
	for (i=NUM_AMBIENTS+ NUM_MUSICS ; i<sc->total_chans; i++, ch++)
	{
		if (!ch->sfx)
			continue;

		SND_Spatialize(sc, ch);         // respatialize channel
		if (!ch->vol[0] && !ch->vol[1] && !ch->vol[2] && !ch->vol[3] && !ch->vol[4] && !ch->vol[5])
			continue;

	// try to combine static sounds with a previous channel of the same
	// sound effect so we don't mix five torches every frame
	
		if (i > MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS + NUM_MUSICS)
		{
		// see if it can just use the last one
			if (combine && combine->sfx == ch->sfx)
			{
				combine->vol[0] += ch->vol[0];
				combine->vol[1] += ch->vol[1];
				combine->vol[2] += ch->vol[2];
				combine->vol[3] += ch->vol[3];
				combine->vol[4] += ch->vol[4];
				combine->vol[5] += ch->vol[5];
				ch->vol[0] = ch->vol[1] = ch->vol[2] = ch->vol[3] = ch->vol[4] = ch->vol[5] = 0;
				continue;
			}
		// search for one
			combine = sc->channel+MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS + NUM_MUSICS;
			for (j=MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS+ NUM_MUSICS ; j<i; j++, combine++)
				if (combine->sfx == ch->sfx)
					break;
					
			if (j == sc->total_chans)
			{
				combine = NULL;
			}
			else
			{
				if (combine != ch)
				{
					combine->vol[0] += ch->vol[0];
					combine->vol[1] += ch->vol[1];
					combine->vol[2] += ch->vol[2];
					combine->vol[3] += ch->vol[3];
					combine->vol[4] += ch->vol[4];
					combine->vol[5] += ch->vol[5];
					ch->vol[0] = ch->vol[1] = ch->vol[2] = ch->vol[3] = ch->vol[4] = ch->vol[5] = 0;
				}
				continue;
			}
		}
	
		
	}

//
// debugging output
//
	if (snd_show.value)
	{
		total = 0;
		ch = sc->channel;
		for (i=0 ; i<sc->total_chans; i++, ch++)
			if (ch->sfx && (ch->vol[0] || ch->vol[1]) )
			{
//					Con_Printf ("%i, %i %i %i %i %i %i %s\n", i, ch->vol[0], ch->vol[1], ch->vol[2], ch->vol[3], ch->vol[4], ch->vol[5], ch->sfx->name);
				total++;
			}
		
		Con_Printf ("----(%i)----\n", total);
	}

// mix some sound	
	S_Update_(sc);
}

void GetSoundtime(soundcardinfo_t *sc)
{
	int		samplepos;	
	int		fullsamples;
	
	fullsamples = sc->sn.samples / sc->sn.numchannels;

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

	if (samplepos < sc->oldsamplepos)
	{
		sc->buffers++;					// buffer wrapped
		
		if (sc->paintedtime > 0x40000000)
		{	// time to chop things off to avoid 32 bit limits
			sc->buffers = 0;
			sc->paintedtime = fullsamples;
			S_StopAllSounds (true);
		}

		sc->rawstart =0;
	}
	else
		sc->rawstart += samplepos - sc->oldsamplepos;
	sc->oldsamplepos = samplepos;

	soundtime = sc->buffers*fullsamples + samplepos/sc->sn.numchannels;
}

void S_ExtraUpdate (void)
{
	soundcardinfo_t *sc;

	if (!sound_started)
		return;

#ifdef _WIN32
	IN_Accumulate ();
#endif

	if (snd_noextraupdate.value)
		return;		// don't pollute timings

	S_UpdateCapture();

	for (sc = sndcardinfo; sc; sc = sc->next)
		S_Update_(sc);
}



void S_Update_(soundcardinfo_t *sc)
{
	unsigned        endtime;
	int				samps;


	if (sc->selfpainting)
		return;

	if ((snd_blocked > 0))
	{
		if (!sc->inactive_sound)
			return;
	}

// Updates DMA time
	GetSoundtime(sc);

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

// mix ahead of current position
	endtime = soundtime + _snd_mixahead.value * sc->sn.speed;
//	samps = shm->samples >> (shm->numchannels-1);
	samps = sc->sn.samples / sc->sn.numchannels;
	if (endtime - soundtime > samps)
		endtime = soundtime + samps;

#if defined(_WIN32) && !defined(NODIRECTX)
// if the buffer was lost or stopped, restore it and/or restart it
	{
		DWORD	dwStatus;

		if (sc->pDSBuf)
		{
			if (sc->pDSBuf->lpVtbl->GetStatus (sc->pDSBuf, &dwStatus) != DD_OK)
				Con_Printf ("Couldn't get sound buffer status\n");
			
			if (dwStatus & DSBSTATUS_BUFFERLOST)
				sc->pDSBuf->lpVtbl->Restore (sc->pDSBuf);
			
			if (!(dwStatus & DSBSTATUS_PLAYING))
				sc->pDSBuf->lpVtbl->Play(sc->pDSBuf, 0, 0, DSBPLAY_LOOPING);
		}
	}
#endif

	S_PaintChannels (sc, endtime);

	SNDDMA_Submit (sc);
}

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

console functions

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

void S_Play(void)
{
//	static int hash=345;
	int 	i;
	char name[256];
	sfx_t	*sfx;
	
	i = 1;
	while (i<Cmd_Argc())
	{
		if (!Q_strrchr(Cmd_Argv(i), '.'))
		{
			Q_strcpy(name, Cmd_Argv(i));
			Q_strcat(name, ".wav");
		}
		else
			Q_strcpy(name, Cmd_Argv(i));
		sfx = S_PrecacheSound(name);
		S_StartSound(cl.playernum[0]+1, -1, sfx, vec3_origin, 1.0, 0.0);
//		hash++;
		i++;
	}
}

void S_PlayVol(void)
{
//	static int hash=543;
	int i;
	float vol;
	char name[256];
	sfx_t	*sfx;
	
	i = 1;
	while (i<Cmd_Argc())
	{
		if (!Q_strrchr(Cmd_Argv(i), '.'))
		{
			Q_strcpy(name, Cmd_Argv(i));
			Q_strcat(name, ".wav");
		}
		else
			Q_strcpy(name, Cmd_Argv(i));
		sfx = S_PrecacheSound(name);
		vol = Q_atof(Cmd_Argv(i+1));
		S_StartSound(cl.playernum[0]+1, -1, sfx, vec3_origin, vol, 0.0);
//		hash;
		i+=2;
	}
}

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 = Cache_Check (&sfx->cache);
		if (!sc)
			continue;
		size = sc->length*sc->width*(sc->stereo+1);
		total += size;
		if (sc->loopstart >= 0)
			Con_Printf ("L");
		else
			Con_Printf (" ");
		Con_Printf("(%2db) %6i : %s\n",sc->width*8,  size, sfx->name);
	}
	Con_Printf ("Total resident: %i\n", total);
}


void S_LocalSound (char *sound)
{
	sfx_t	*sfx;

	if (nosound.value)
		return;
	if (!sound_started)
		return;

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


void S_ClearPrecache (void)
{
}


void S_BeginPrecaching (void)
{
}


void S_EndPrecaching (void)
{
}













typedef struct {
	qboolean inuse;
	int id;
	sfx_t sfx;
	sfxcache_t *sfxcache;
} streaming_t;
#define MAX_RAW_SOURCES (MAX_CLIENTS+1)
streaming_t s_streamers[MAX_RAW_SOURCES];

/*
qboolean S_IsPlayingSomewhere(sfx_t *s)
{
	soundcardinfo_t *si;
	int i;
	for (si = sndcardinfo; si; si=si->next)
	{
		for (i = 0; i < MAX_CHANNELS; i++)
		if (si->channel[i].sfx == s)
			return true;
	}
	return false;
}*/
#undef free

//streaming audio.	//this is useful when there is one source, and the sound is to be played with no attenuation
void S_RawAudio(int sourceid, qbyte *data, int speed, int samples, int channels, int width)
{
	soundcardinfo_t *si;
	int i;
	int prepadl;
	int spare;
	float speedfactor;
	sfxcache_t *newcache;
	streaming_t *s, *free=NULL;
	samples/=channels;
	for (s = s_streamers, i = 0; i < MAX_RAW_SOURCES; i++, s++)
	{
		if (!s->inuse)
		{
			free = s;
			continue;
		}
		if (s->id == sourceid)
			break;
	}
	if (i == MAX_RAW_SOURCES)	//whoops.
	{
		if (!free)
		{
			Con_Printf("No free audio streams\n");
			return;
		}
		s = free;
		free->id = sourceid;
		free->inuse = true;
		free->sfx.cache.fake = true;
		strcpy(free->sfx.name, "");
		free->sfxcache = BZ_Malloc(sizeof(sfxcache_t));
		free->sfx.cache.data = free->sfxcache;
		free->sfxcache->speed = snd_speed;
		free->sfxcache->stereo = channels-1;
		free->sfxcache->width = width;
		free->sfxcache->loopstart = -1;
		free->sfxcache->length = 0;
	}
	if (s->sfxcache->width != width || s->sfxcache->stereo != channels-1 || s->sfxcache->speed != snd_speed)
	{
		s->sfxcache->width = width;
		s->sfxcache->stereo = channels-1;
		s->sfxcache->speed = snd_speed;
		s->sfxcache->length = 0;
	}

	prepadl = -0x7fffffff;
	for (si = sndcardinfo; si; si=si->next)	//make sure all cards are playing, and that we still get a prepad if just one is.
	{
		for (i = 0; i < MAX_CHANNELS; i++)
			if (si->channel[i].sfx == &s->sfx)
			{
				if (prepadl < si->channel[i].pos)
					prepadl = si->channel[i].pos;
				break;
			}
	}

	if (prepadl == -0x7fffffff)
	{
		prepadl = 0;
		spare = s->sfxcache->length;
		if (spare > snd_speed)
			spare = 0;	//too far out. sacrifice it all
	}
	else
	{
		spare = s->sfxcache->length - prepadl;
		if (spare < 0)
			spare = 0;
	}

/*	else if (spare > snd_speed)
	{
		for (si = sndcardinfo; si; si=si->next)
		{
			for (i = 0; i < MAX_CHANNELS; i++)
				if (si->channel[i].sfx == &s->sfx)
				{
					break;
				}
			if (i == MAX_CHANNELS)	//this one wasn't playing.
				S_StartSoundCard(si, -1, 0, &s->sfx, r_origin, 1, 32767, prepadl);
		}
		return;	//let the slower sound cards catch up. (This shouldn't really happen, but it's possible two cards have slightly different timings but report the same speed)
	}*/

	newcache = BZ_Malloc(sizeof(sfxcache_t) + (spare+samples) * (s->sfxcache->stereo+1) * s->sfxcache->width);
	memcpy(newcache, s->sfxcache, sizeof(sfxcache_t));
	memcpy(newcache->data, s->sfxcache->data + prepadl * (s->sfxcache->stereo+1) * s->sfxcache->width, spare * (s->sfxcache->stereo+1) * s->sfxcache->width);

	BZ_Free(s->sfxcache);
	s->sfxcache = s->sfx.cache.data = newcache;

	newcache->length = spare + samples;

	speedfactor	= (float)snd_speed/speed;
	if (channels == 1)
	{
		if (width == 2)
		{
			short sample;
			short *indata = (short *)data;
			short *outpos = (short *)(newcache->data + spare * (s->sfxcache->stereo+1) * s->sfxcache->width);
			if (speedfactor==1)	//fast
			{
				while (samples--)
				{
					sample = *indata++;
					*outpos++ = sample;
				}
			}
			else
			{
				int src=0;
				int pos=0;			

				while (pos++ < samples)
				{
					src = pos/speedfactor;
					sample = indata[src];
					*outpos++ = sample;
				}
			}
		}
		else if (width == 1)
		{
			char sample;
			char *indata = (char *)data;
			char *outpos = (char *)(newcache->data + spare * (s->sfxcache->stereo+1) * s->sfxcache->width);
			if (speedfactor==1)	//fast
			{
				while (samples--)
				{
					sample = *indata++;
					*outpos++ = (int)( (unsigned char)(sample) - 128);
				}
			}
			else
			{
				int src=0;
				int pos=0;			

				while (pos++ < samples)
				{
					src = pos/speedfactor;
					sample = indata[src];
					*outpos++ = (int)( (unsigned char)(sample) - 128);
				}
			}
		}
		else
			Sys_Error("Width isn't 2\n");
	}
	else
	{
		if (width == 2)
		{
			short sample;
			short *indata = (short *)data;
			short *outpos = (short *)((qbyte *)newcache->data + spare * (s->sfxcache->stereo+1) * s->sfxcache->width);
			if (speedfactor==1)	//fast
			{
				while (samples--)
				{
					sample = *indata++;
					*outpos++ = sample;

					sample = *indata++;
					*outpos++ = sample;
				}
			}
			else
			{
				int src=0;
				int pos=0;			

				while (pos++ < samples)
				{
					src = pos/speedfactor;
					sample = indata[src*2];
					*outpos++ = sample;

					sample = indata[src*2+1];
					*outpos++ = sample;
				}
			}
		}
		else
			Sys_Error("Width isn't 2\n");
	}


	for (si = sndcardinfo; si; si=si->next)
	{
		for (i = 0; i < MAX_CHANNELS; i++)
			if (si->channel[i].sfx == &s->sfx)
			{
				si->channel[i].pos -= prepadl;
				si->channel[i].end += samples - prepadl;
				break;
			}
		if (i == MAX_CHANNELS)	//this one wasn't playing.
			S_StartSoundCard(si, -1, 0, &s->sfx, r_origin, 1, 32767, 0);
	}
}

#endif	//cygwin
