1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20// snd_dma.c -- main control for any streaming sound output device
21
22#include "quakedef.h"
23
24#ifdef _WIN32
25#include "winquake.h"
26#endif
27
28void S_Play(void);
29void S_PlayVol(void);
30void S_SoundList(void);
31void S_Update_();
32void S_StopAllSounds(qboolean clear);
33void S_StopAllSoundsC(void);
34
35// QuakeWorld hack...
36#define	viewentity	playernum+1
37
38// =======================================================================
39// Internal sound data & structures
40// =======================================================================
41
42channel_t   channels[MAX_CHANNELS];
43int			total_channels;
44
45int				snd_blocked = 0;
46static qboolean	snd_ambient = 1;
47qboolean		snd_initialized = false;
48
49// pointer should go away
50volatile dma_t  *shm = 0;
51volatile dma_t sn;
52
53vec3_t		listener_origin;
54vec3_t		listener_forward;
55vec3_t		listener_right;
56vec3_t		listener_up;
57vec_t		sound_nominal_clip_dist=1000.0;
58
59int			soundtime;		// sample PAIRS
60int   		paintedtime; 	// sample PAIRS
61
62
63#define	MAX_SFX		512
64sfx_t		*known_sfx;		// hunk allocated [MAX_SFX]
65int			num_sfx;
66
67sfx_t		*ambient_sfx[NUM_AMBIENTS];
68
69int 		desired_speed = 11025;
70int 		desired_bits = 16;
71
72int sound_started=0;
73
74cvar_t bgmvolume = CVAR3("bgmvolume", "1", true);
75cvar_t volume = CVAR3("volume", "0.7", true);
76
77cvar_t nosound = CVAR2("nosound", "0");
78cvar_t precache = CVAR2("precache", "1");
79cvar_t loadas8bit = CVAR2("loadas8bit", "0");
80cvar_t bgmbuffer = CVAR2("bgmbuffer", "4096");
81cvar_t ambient_level = CVAR2("ambient_level", "0.3");
82cvar_t ambient_fade = CVAR2("ambient_fade", "100");
83cvar_t snd_noextraupdate = CVAR2("snd_noextraupdate", "0");
84cvar_t snd_show = CVAR2("snd_show", "0");
85cvar_t _snd_mixahead = CVAR3("_snd_mixahead", "0.1", true);
86
87
88// ====================================================================
89// User-setable variables
90// ====================================================================
91
92
93//
94// Fake dma is a synchronous faking of the DMA progress used for
95// isolating performance in the renderer.  The fakedma_updates is
96// number of times S_Update() is called per second.
97//
98
99qboolean fakedma = false;
100int fakedma_updates = 15;
101
102
103void S_AmbientOff (void)
104{
105	snd_ambient = false;
106}
107
108
109void S_AmbientOn (void)
110{
111	snd_ambient = true;
112}
113
114
115void S_SoundInfo_f(void)
116{
117	if (!sound_started || !shm)
118	{
119		Con_Printf ("sound system not started\n");
120		return;
121	}
122
123    Con_Printf("%5d stereo\n", shm->channels - 1);
124    Con_Printf("%5d samples\n", shm->samples);
125    Con_Printf("%5d samplepos\n", shm->samplepos);
126    Con_Printf("%5d samplebits\n", shm->samplebits);
127    Con_Printf("%5d submission_chunk\n", shm->submission_chunk);
128    Con_Printf("%5d speed\n", shm->speed);
129    Con_Printf("0x%x dma buffer\n", shm->buffer);
130	Con_Printf("%5d total_channels\n", total_channels);
131}
132
133
134/*
135================
136S_Startup
137================
138*/
139
140void S_Startup (void)
141{
142	int		rc;
143
144	if (!snd_initialized)
145		return;
146
147	if (!fakedma)
148	{
149		rc = SNDDMA_Init();
150
151		if (!rc)
152		{
153#ifndef	_WIN32
154			Con_Printf("S_Startup: SNDDMA_Init failed.\n");
155#endif
156			sound_started = 0;
157			return;
158		}
159	}
160
161	sound_started = 1;
162}
163
164
165/*
166================
167S_Init
168================
169*/
170void S_Init (void)
171{
172
173//	Con_Printf("\nSound Initialization\n");
174
175	if (COM_CheckParm("-nosound"))
176		return;
177
178	if (COM_CheckParm("-simsound"))
179		fakedma = true;
180
181	Cmd_AddCommand("play", S_Play);
182	Cmd_AddCommand("playvol", S_PlayVol);
183	Cmd_AddCommand("stopsound", S_StopAllSoundsC);
184	Cmd_AddCommand("soundlist", S_SoundList);
185	Cmd_AddCommand("soundinfo", S_SoundInfo_f);
186
187	Cvar_RegisterVariable(&nosound);
188	Cvar_RegisterVariable(&volume);
189	Cvar_RegisterVariable(&precache);
190	Cvar_RegisterVariable(&loadas8bit);
191	Cvar_RegisterVariable(&bgmvolume);
192	Cvar_RegisterVariable(&bgmbuffer);
193	Cvar_RegisterVariable(&ambient_level);
194	Cvar_RegisterVariable(&ambient_fade);
195	Cvar_RegisterVariable(&snd_noextraupdate);
196	Cvar_RegisterVariable(&snd_show);
197	Cvar_RegisterVariable(&_snd_mixahead);
198
199	if (host_parms.memsize < 0x800000)
200	{
201		Cvar_Set ("loadas8bit", "1");
202		Con_Printf ("loading all sounds as 8bit\n");
203	}
204
205
206
207	snd_initialized = true;
208
209	S_Startup ();
210
211	SND_InitScaletable ();
212
213	known_sfx = Hunk_AllocName (MAX_SFX*sizeof(sfx_t), "sfx_t");
214	num_sfx = 0;
215
216// create a piece of DMA memory
217
218	if (fakedma)
219	{
220		shm = (void *) Hunk_AllocName(sizeof(*shm), "shm");
221		shm->splitbuffer = 0;
222		shm->samplebits = 16;
223		shm->speed = 22050;
224		shm->channels = 2;
225		shm->samples = 32768;
226		shm->samplepos = 0;
227		shm->soundalive = true;
228		shm->gamealive = true;
229		shm->submission_chunk = 1;
230		shm->buffer = Hunk_AllocName(1<<16, "shmbuf");
231	}
232
233//	Con_Printf ("Sound sampling rate: %i\n", shm->speed);
234
235	// provides a tick sound until washed clean
236
237//	if (shm->buffer)
238//		shm->buffer[4] = shm->buffer[5] = 0x7f;	// force a pop for debugging
239
240	ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav");
241	ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav");
242
243	S_StopAllSounds (true);
244}
245
246
247// =======================================================================
248// Shutdown sound engine
249// =======================================================================
250
251void S_Shutdown(void)
252{
253
254	if (!sound_started)
255		return;
256
257	if (shm)
258		shm->gamealive = 0;
259
260	shm = 0;
261	sound_started = 0;
262
263	if (!fakedma)
264	{
265		SNDDMA_Shutdown();
266	}
267}
268
269
270// =======================================================================
271// Load a sound
272// =======================================================================
273
274/*
275==================
276S_FindName
277
278==================
279*/
280sfx_t *S_FindName (char *name)
281{
282	int		i;
283	sfx_t	*sfx;
284
285	if (!name)
286		Sys_Error ("S_FindName: NULL\n");
287
288	if (Q_strlen(name) >= MAX_QPATH)
289		Sys_Error ("Sound name too long: %s", name);
290
291// see if already loaded
292	for (i=0 ; i < num_sfx ; i++)
293		if (!Q_strcmp(known_sfx[i].name, name))
294		{
295			return &known_sfx[i];
296		}
297
298	if (num_sfx == MAX_SFX)
299		Sys_Error ("S_FindName: out of sfx_t");
300
301	sfx = &known_sfx[i];
302	strcpy (sfx->name, name);
303
304	num_sfx++;
305
306	return sfx;
307}
308
309
310/*
311==================
312S_TouchSound
313
314==================
315*/
316void S_TouchSound (char *name)
317{
318	sfx_t	*sfx;
319
320	if (!sound_started)
321		return;
322
323	sfx = S_FindName (name);
324	Cache_Check (&sfx->cache);
325}
326
327/*
328==================
329S_PrecacheSound
330
331==================
332*/
333sfx_t *S_PrecacheSound (char *name)
334{
335	sfx_t	*sfx;
336
337	if (!sound_started || nosound.value)
338		return NULL;
339
340	sfx = S_FindName (name);
341
342// cache it in
343	if (precache.value)
344		S_LoadSound (sfx);
345
346	return sfx;
347}
348
349
350//=============================================================================
351
352/*
353=================
354SND_PickChannel
355=================
356*/
357channel_t *SND_PickChannel(int entnum, int entchannel)
358{
359    int ch_idx;
360    int first_to_die;
361    int life_left;
362
363// Check for replacement sound, or find the best one to replace
364    first_to_die = -1;
365    life_left = 0x7fffffff;
366    for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++)
367    {
368		if (entchannel != 0		// channel 0 never overrides
369		&& channels[ch_idx].entnum == entnum
370		&& (channels[ch_idx].entchannel == entchannel || entchannel == -1) )
371		{	// allways override sound from same entity
372			first_to_die = ch_idx;
373			break;
374		}
375
376		// don't let monster sounds override player sounds
377		if (channels[ch_idx].entnum == cl.viewentity && entnum != cl.viewentity && channels[ch_idx].sfx)
378			continue;
379
380		if (channels[ch_idx].end - paintedtime < life_left)
381		{
382			life_left = channels[ch_idx].end - paintedtime;
383			first_to_die = ch_idx;
384		}
385   }
386
387	if (first_to_die == -1)
388		return NULL;
389
390	if (channels[first_to_die].sfx)
391		channels[first_to_die].sfx = NULL;
392
393    return &channels[first_to_die];
394}
395
396/*
397=================
398SND_Spatialize
399=================
400*/
401void SND_Spatialize(channel_t *ch)
402{
403    vec_t dot;
404    vec_t dist;
405    vec_t lscale, rscale, scale;
406    vec3_t source_vec;
407	sfx_t *snd;
408
409// anything coming from the view entity will allways be full volume
410	if (ch->entnum == cl.viewentity)
411	{
412		ch->leftvol = ch->master_vol;
413		ch->rightvol = ch->master_vol;
414		return;
415	}
416
417// calculate stereo seperation and distance attenuation
418
419	snd = ch->sfx;
420	VectorSubtract(ch->origin, listener_origin, source_vec);
421
422	dist = VectorNormalize(source_vec) * ch->dist_mult;
423
424	dot = DotProduct(listener_right, source_vec);
425
426	if (shm->channels == 1)
427	{
428		rscale = 1.0;
429		lscale = 1.0;
430	}
431	else
432	{
433		rscale = 1.0 + dot;
434		lscale = 1.0 - dot;
435	}
436
437// add in distance effect
438	scale = (1.0 - dist) * rscale;
439	ch->rightvol = (int) (ch->master_vol * scale);
440	if (ch->rightvol < 0)
441		ch->rightvol = 0;
442
443	scale = (1.0 - dist) * lscale;
444	ch->leftvol = (int) (ch->master_vol * scale);
445	if (ch->leftvol < 0)
446		ch->leftvol = 0;
447}
448
449
450// =======================================================================
451// Start a sound effect
452// =======================================================================
453
454void S_StartSound(int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation)
455{
456	channel_t *target_chan, *check;
457	sfxcache_t	*sc;
458	int		vol;
459	int		ch_idx;
460	int		skip;
461
462	if (!sound_started)
463		return;
464
465	if (!sfx)
466		return;
467
468	if (nosound.value)
469		return;
470
471	vol = fvol*255;
472
473// pick a channel to play on
474	target_chan = SND_PickChannel(entnum, entchannel);
475	if (!target_chan)
476		return;
477
478// spatialize
479	memset (target_chan, 0, sizeof(*target_chan));
480	VectorCopy(origin, target_chan->origin);
481	target_chan->dist_mult = attenuation / sound_nominal_clip_dist;
482	target_chan->master_vol = vol;
483	target_chan->entnum = entnum;
484	target_chan->entchannel = entchannel;
485	SND_Spatialize(target_chan);
486
487	if (!target_chan->leftvol && !target_chan->rightvol)
488		return;		// not audible at all
489
490// new channel
491	sc = S_LoadSound (sfx);
492	if (!sc)
493	{
494		target_chan->sfx = NULL;
495		return;		// couldn't load the sound's data
496	}
497
498	target_chan->sfx = sfx;
499	target_chan->pos = 0.0;
500    target_chan->end = paintedtime + sc->length;
501
502// if an identical sound has also been started this frame, offset the pos
503// a bit to keep it from just making the first one louder
504	check = &channels[NUM_AMBIENTS];
505    for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++, check++)
506    {
507		if (check == target_chan)
508			continue;
509		if (check->sfx == sfx && !check->pos)
510		{
511			skip = rand () % (int)(0.1*shm->speed);
512			if (skip >= target_chan->end)
513				skip = target_chan->end - 1;
514			target_chan->pos += skip;
515			target_chan->end -= skip;
516			break;
517		}
518
519	}
520}
521
522void S_StopSound(int entnum, int entchannel)
523{
524	int i;
525
526	for (i=0 ; i<MAX_DYNAMIC_CHANNELS ; i++)
527	{
528		if (channels[i].entnum == entnum
529			&& channels[i].entchannel == entchannel)
530		{
531			channels[i].end = 0;
532			channels[i].sfx = NULL;
533			return;
534		}
535	}
536}
537
538void S_StopAllSounds(qboolean clear)
539{
540	int		i;
541
542	if (!sound_started)
543		return;
544
545	total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;	// no statics
546
547	for (i=0 ; i<MAX_CHANNELS ; i++)
548		if (channels[i].sfx)
549			channels[i].sfx = NULL;
550
551	Q_memset(channels, 0, MAX_CHANNELS * sizeof(channel_t));
552
553	if (clear)
554		S_ClearBuffer ();
555}
556
557void S_StopAllSoundsC (void)
558{
559	S_StopAllSounds (true);
560}
561
562void S_ClearBuffer (void)
563{
564	int		clear;
565
566#ifdef _WIN32
567	if (!sound_started || !shm || (!shm->buffer && !pDSBuf))
568#else
569	if (!sound_started || !shm || !shm->buffer)
570#endif
571		return;
572
573	if (shm->samplebits == 8)
574		clear = 0x80;
575	else
576		clear = 0;
577
578#ifdef _WIN32
579	if (pDSBuf)
580	{
581		DWORD	dwSize;
582		DWORD	*pData;
583		int		reps;
584		HRESULT	hresult;
585
586		reps = 0;
587
588		while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &pData, &dwSize, NULL, NULL, 0)) != DS_OK)
589		{
590			if (hresult != DSERR_BUFFERLOST)
591			{
592				Con_Printf ("S_ClearBuffer: DS::Lock Sound Buffer Failed\n");
593				S_Shutdown ();
594				return;
595			}
596
597			if (++reps > 10000)
598			{
599				Con_Printf ("S_ClearBuffer: DS: couldn't restore buffer\n");
600				S_Shutdown ();
601				return;
602			}
603		}
604
605		Q_memset(pData, clear, shm->samples * shm->samplebits/8);
606
607		pDSBuf->lpVtbl->Unlock(pDSBuf, pData, dwSize, NULL, 0);
608
609	}
610	else
611#endif
612	{
613		Q_memset(shm->buffer, clear, shm->samples * shm->samplebits/8);
614	}
615}
616
617
618/*
619=================
620S_StaticSound
621=================
622*/
623void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation)
624{
625	channel_t	*ss;
626	sfxcache_t		*sc;
627
628	if (!sfx)
629		return;
630
631	if (total_channels == MAX_CHANNELS)
632	{
633		Con_Printf ("total_channels == MAX_CHANNELS\n");
634		return;
635	}
636
637	ss = &channels[total_channels];
638	total_channels++;
639
640	sc = S_LoadSound (sfx);
641	if (!sc)
642		return;
643
644	if (sc->loopstart == -1)
645	{
646		Con_Printf ("Sound %s not looped\n", sfx->name);
647		return;
648	}
649
650	ss->sfx = sfx;
651	VectorCopy (origin, ss->origin);
652	ss->master_vol = vol;
653	ss->dist_mult = (attenuation/64) / sound_nominal_clip_dist;
654    ss->end = paintedtime + sc->length;
655
656	SND_Spatialize (ss);
657}
658
659
660//=============================================================================
661
662/*
663===================
664S_UpdateAmbientSounds
665===================
666*/
667void S_UpdateAmbientSounds (void)
668{
669	mleaf_t		*l;
670	float		vol;
671	int			ambient_channel;
672	channel_t	*chan;
673
674	if (!snd_ambient)
675		return;
676
677// calc ambient sound levels
678	if (!cl.worldmodel)
679		return;
680
681	l = Mod_PointInLeaf (listener_origin, cl.worldmodel);
682	if (!l || !ambient_level.value)
683	{
684		for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++)
685			channels[ambient_channel].sfx = NULL;
686		return;
687	}
688
689	for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++)
690	{
691		chan = &channels[ambient_channel];
692		chan->sfx = ambient_sfx[ambient_channel];
693
694		vol = ambient_level.value * l->ambient_sound_level[ambient_channel];
695		if (vol < 8)
696			vol = 0;
697
698	// don't adjust volume too fast
699		if (chan->master_vol < vol)
700		{
701			chan->master_vol += host_frametime * ambient_fade.value;
702			if (chan->master_vol > vol)
703				chan->master_vol = vol;
704		}
705		else if (chan->master_vol > vol)
706		{
707			chan->master_vol -= host_frametime * ambient_fade.value;
708			if (chan->master_vol < vol)
709				chan->master_vol = vol;
710		}
711
712		chan->leftvol = chan->rightvol = chan->master_vol;
713	}
714}
715
716
717/*
718============
719S_Update
720
721Called once each time through the main loop
722============
723*/
724void S_Update(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up)
725{
726	int			i, j;
727	int			total;
728	channel_t	*ch;
729	channel_t	*combine;
730
731	if (!sound_started || (snd_blocked > 0))
732		return;
733
734	VectorCopy(origin, listener_origin);
735	VectorCopy(forward, listener_forward);
736	VectorCopy(right, listener_right);
737	VectorCopy(up, listener_up);
738
739// update general area ambient sound sources
740	S_UpdateAmbientSounds ();
741
742	combine = NULL;
743
744// update spatialization for static and dynamic sounds
745	ch = channels+NUM_AMBIENTS;
746	for (i=NUM_AMBIENTS ; i<total_channels; i++, ch++)
747	{
748		if (!ch->sfx)
749			continue;
750		SND_Spatialize(ch);         // respatialize channel
751		if (!ch->leftvol && !ch->rightvol)
752			continue;
753
754	// try to combine static sounds with a previous channel of the same
755	// sound effect so we don't mix five torches every frame
756
757		if (i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS)
758		{
759		// see if it can just use the last one
760			if (combine && combine->sfx == ch->sfx)
761			{
762				combine->leftvol += ch->leftvol;
763				combine->rightvol += ch->rightvol;
764				ch->leftvol = ch->rightvol = 0;
765				continue;
766			}
767		// search for one
768			combine = channels+MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;
769			for (j=MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS ; j<i; j++, combine++)
770				if (combine->sfx == ch->sfx)
771					break;
772
773			if (j == total_channels)
774			{
775				combine = NULL;
776			}
777			else
778			{
779				if (combine != ch)
780				{
781					combine->leftvol += ch->leftvol;
782					combine->rightvol += ch->rightvol;
783					ch->leftvol = ch->rightvol = 0;
784				}
785				continue;
786			}
787		}
788
789
790	}
791
792//
793// debugging output
794//
795	if (snd_show.value)
796	{
797		total = 0;
798		ch = channels;
799		for (i=0 ; i<total_channels; i++, ch++)
800			if (ch->sfx && (ch->leftvol || ch->rightvol) )
801			{
802				//Con_Printf ("%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name);
803				total++;
804			}
805
806		Con_Printf ("----(%i)----\n", total);
807	}
808
809// mix some sound
810	S_Update_();
811}
812
813void GetSoundtime(void)
814{
815	int		samplepos;
816	static	int		buffers;
817	static	int		oldsamplepos;
818	int		fullsamples;
819
820	fullsamples = shm->samples / shm->channels;
821
822// it is possible to miscount buffers if it has wrapped twice between
823// calls to S_Update.  Oh well.
824	samplepos = SNDDMA_GetDMAPos();
825
826	if (samplepos < oldsamplepos)
827	{
828		buffers++;					// buffer wrapped
829
830		if (paintedtime > 0x40000000)
831		{	// time to chop things off to avoid 32 bit limits
832			buffers = 0;
833			paintedtime = fullsamples;
834			S_StopAllSounds (true);
835		}
836	}
837	oldsamplepos = samplepos;
838
839	soundtime = buffers*fullsamples + samplepos/shm->channels;
840}
841
842void S_ExtraUpdate (void)
843{
844
845#ifdef _WIN32
846	IN_Accumulate ();
847#endif
848
849	if (snd_noextraupdate.value)
850		return;		// don't pollute timings
851	S_Update_();
852}
853
854
855
856void S_Update_(void)
857{
858	unsigned        endtime;
859	int				samps;
860
861	if (!sound_started || (snd_blocked > 0))
862		return;
863
864// Updates DMA time
865	GetSoundtime();
866
867// check to make sure that we haven't overshot
868	if (paintedtime < soundtime)
869	{
870		//Con_Printf ("S_Update_ : overflow\n");
871		paintedtime = soundtime;
872	}
873
874// mix ahead of current position
875	endtime = soundtime + _snd_mixahead.value * shm->speed;
876	samps = shm->samples >> (shm->channels-1);
877	if ((int)(endtime - soundtime) > samps)
878		endtime = soundtime + samps;
879
880#ifdef _WIN32
881// if the buffer was lost or stopped, restore it and/or restart it
882	{
883		DWORD	dwStatus;
884
885		if (pDSBuf)
886		{
887			if (pDSBuf->lpVtbl->GetStatus (pDSBuf, &dwStatus) != DD_OK)
888				Con_Printf ("Couldn't get sound buffer status\n");
889
890			if (dwStatus & DSBSTATUS_BUFFERLOST)
891				pDSBuf->lpVtbl->Restore (pDSBuf);
892
893			if (!(dwStatus & DSBSTATUS_PLAYING))
894				pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);
895		}
896	}
897#endif
898
899	S_PaintChannels (endtime);
900
901	SNDDMA_Submit ();
902}
903
904/*
905===============================================================================
906
907console functions
908
909===============================================================================
910*/
911
912void S_Play(void)
913{
914	static int hash=345;
915	int 	i;
916	char name[256];
917	sfx_t	*sfx;
918
919	i = 1;
920	while (i<Cmd_Argc())
921	{
922		if (!Q_strrchr(Cmd_Argv(i), '.'))
923		{
924			Q_strcpy(name, Cmd_Argv(i));
925			Q_strcat(name, ".wav");
926		}
927		else
928			Q_strcpy(name, Cmd_Argv(i));
929		sfx = S_PrecacheSound(name);
930		S_StartSound(hash++, 0, sfx, listener_origin, 1.0, 1.0);
931		i++;
932	}
933}
934
935void S_PlayVol(void)
936{
937	static int hash=543;
938	int i;
939	float vol;
940	char name[256];
941	sfx_t	*sfx;
942
943	i = 1;
944	while (i<Cmd_Argc())
945	{
946		if (!Q_strrchr(Cmd_Argv(i), '.'))
947		{
948			Q_strcpy(name, Cmd_Argv(i));
949			Q_strcat(name, ".wav");
950		}
951		else
952			Q_strcpy(name, Cmd_Argv(i));
953		sfx = S_PrecacheSound(name);
954		vol = Q_atof(Cmd_Argv(i+1));
955		S_StartSound(hash++, 0, sfx, listener_origin, vol, 1.0);
956		i+=2;
957	}
958}
959
960void S_SoundList(void)
961{
962	int		i;
963	sfx_t	*sfx;
964	sfxcache_t	*sc;
965	int		size, total;
966
967	total = 0;
968	for (sfx=known_sfx, i=0 ; i<num_sfx ; i++, sfx++)
969	{
970		sc = Cache_Check (&sfx->cache);
971		if (!sc)
972			continue;
973		size = sc->length*sc->width*(sc->stereo+1);
974		total += size;
975		if (sc->loopstart >= 0)
976			Con_Printf ("L");
977		else
978			Con_Printf (" ");
979		Con_Printf("(%2db) %6i : %s\n",sc->width*8,  size, sfx->name);
980	}
981	Con_Printf ("Total resident: %i\n", total);
982}
983
984
985void S_LocalSound (char *sound)
986{
987	sfx_t	*sfx;
988
989	if (nosound.value)
990		return;
991	if (!sound_started)
992		return;
993
994	sfx = S_PrecacheSound (sound);
995	if (!sfx)
996	{
997		Con_Printf ("S_LocalSound: can't cache %s\n", sound);
998		return;
999	}
1000	S_StartSound (cl.viewentity, -1, sfx, vec3_origin, 1, 1);
1001}
1002
1003
1004void S_ClearPrecache (void)
1005{
1006}
1007
1008
1009void S_BeginPrecaching (void)
1010{
1011}
1012
1013
1014void S_EndPrecaching (void)
1015{
1016}
1017
1018