mirror of
https://git.code.sf.net/p/quake/quake2forge
synced 2024-12-13 14:11:35 +00:00
1214 lines
24 KiB
C
1214 lines
24 KiB
C
/*
|
|
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);
|
|
|
|
|
|
// =======================================================================
|
|
// Internal sound data & structures
|
|
// =======================================================================
|
|
|
|
// only begin attenuating sound volumes when outside the FULLVOLUME range
|
|
#define SOUND_FULLVOLUME 80
|
|
|
|
#define SOUND_LOOPATTENUATE 0.003
|
|
|
|
int s_registration_sequence;
|
|
|
|
channel_t channels[MAX_CHANNELS];
|
|
|
|
qboolean snd_initialized = false;
|
|
int sound_started=0;
|
|
|
|
dma_t dma;
|
|
|
|
vec3_t listener_origin;
|
|
vec3_t listener_forward;
|
|
vec3_t listener_right;
|
|
vec3_t listener_up;
|
|
|
|
qboolean s_registering;
|
|
|
|
int soundtime; // sample PAIRS
|
|
int paintedtime; // sample PAIRS
|
|
|
|
// during registration it is possible to have more sounds
|
|
// than could actually be referenced during gameplay,
|
|
// because we don't want to free anything until we are
|
|
// sure we won't need it.
|
|
#define MAX_SFX (MAX_SOUNDS*2)
|
|
sfx_t known_sfx[MAX_SFX];
|
|
int num_sfx;
|
|
|
|
#define MAX_PLAYSOUNDS 128
|
|
playsound_t s_playsounds[MAX_PLAYSOUNDS];
|
|
playsound_t s_freeplays;
|
|
playsound_t s_pendingplays;
|
|
|
|
int s_beginofs;
|
|
|
|
cvar_t *s_volume;
|
|
cvar_t *s_testsound;
|
|
cvar_t *s_loadas8bit;
|
|
cvar_t *s_khz;
|
|
cvar_t *s_show;
|
|
cvar_t *s_mixahead;
|
|
cvar_t *s_primary;
|
|
|
|
|
|
int s_rawend;
|
|
portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
|
|
|
|
|
|
// ====================================================================
|
|
// User-setable variables
|
|
// ====================================================================
|
|
|
|
|
|
void S_SoundInfo_f(void)
|
|
{
|
|
if (!sound_started)
|
|
{
|
|
Com_Printf ("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_Init
|
|
================
|
|
*/
|
|
void S_Init (void)
|
|
{
|
|
cvar_t *cv;
|
|
|
|
Com_Printf("\n------- sound initialization -------\n");
|
|
|
|
cv = Cvar_Get ("s_initsound", "1", 0);
|
|
if (!cv->value)
|
|
Com_Printf ("not initializing.\n");
|
|
else
|
|
{
|
|
s_volume = Cvar_Get ("s_volume", "0.7", CVAR_ARCHIVE);
|
|
s_khz = Cvar_Get ("s_khz", "11", CVAR_ARCHIVE);
|
|
s_loadas8bit = Cvar_Get ("s_loadas8bit", "1", CVAR_ARCHIVE);
|
|
s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE);
|
|
s_show = Cvar_Get ("s_show", "0", 0);
|
|
s_testsound = Cvar_Get ("s_testsound", "0", 0);
|
|
s_primary = Cvar_Get ("s_primary", "0", CVAR_ARCHIVE); // win32 specific
|
|
|
|
Cmd_AddCommand("play", S_Play);
|
|
Cmd_AddCommand("stopsound", S_StopAllSounds);
|
|
Cmd_AddCommand("soundlist", S_SoundList);
|
|
Cmd_AddCommand("soundinfo", S_SoundInfo_f);
|
|
|
|
if (!SNDDMA_Init())
|
|
return;
|
|
|
|
S_InitScaletable ();
|
|
|
|
sound_started = 1;
|
|
num_sfx = 0;
|
|
|
|
soundtime = 0;
|
|
paintedtime = 0;
|
|
|
|
Com_Printf ("sound sampling rate: %i\n", dma.speed);
|
|
|
|
S_StopAllSounds ();
|
|
}
|
|
|
|
Com_Printf("------------------------------------\n");
|
|
}
|
|
|
|
|
|
// =======================================================================
|
|
// Shutdown sound engine
|
|
// =======================================================================
|
|
|
|
void S_Shutdown(void)
|
|
{
|
|
int i;
|
|
sfx_t *sfx;
|
|
|
|
if (!sound_started)
|
|
return;
|
|
|
|
SNDDMA_Shutdown();
|
|
|
|
sound_started = 0;
|
|
|
|
Cmd_RemoveCommand("play");
|
|
Cmd_RemoveCommand("stopsound");
|
|
Cmd_RemoveCommand("soundlist");
|
|
Cmd_RemoveCommand("soundinfo");
|
|
|
|
// free all sounds
|
|
for (i=0, sfx=known_sfx ; i < num_sfx ; i++,sfx++)
|
|
{
|
|
if (!sfx->name[0])
|
|
continue;
|
|
if (sfx->cache)
|
|
Z_Free (sfx->cache);
|
|
memset (sfx, 0, sizeof(*sfx));
|
|
}
|
|
|
|
num_sfx = 0;
|
|
}
|
|
|
|
|
|
// =======================================================================
|
|
// 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\n");
|
|
if (!name[0])
|
|
Com_Error (ERR_FATAL, "S_FindName: empty name\n");
|
|
|
|
if (strlen(name) >= MAX_QPATH)
|
|
Com_Error (ERR_FATAL, "Sound name too long: %s", name);
|
|
|
|
// see if already loaded
|
|
for (i=0 ; i < num_sfx ; i++)
|
|
if (!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])
|
|
// registration_sequence < s_registration_sequence)
|
|
break;
|
|
|
|
if (i == num_sfx)
|
|
{
|
|
if (num_sfx == MAX_SFX)
|
|
Com_Error (ERR_FATAL, "S_FindName: out of sfx_t");
|
|
num_sfx++;
|
|
}
|
|
|
|
sfx = &known_sfx[i];
|
|
memset (sfx, 0, sizeof(*sfx));
|
|
strcpy (sfx->name, name);
|
|
sfx->registration_sequence = s_registration_sequence;
|
|
|
|
return sfx;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
S_AliasName
|
|
|
|
==================
|
|
*/
|
|
sfx_t *S_AliasName (char *aliasname, char *truename)
|
|
{
|
|
sfx_t *sfx;
|
|
char *s;
|
|
int i;
|
|
|
|
s = Z_Malloc (MAX_QPATH);
|
|
strcpy (s, truename);
|
|
|
|
// 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));
|
|
strcpy (sfx->name, aliasname);
|
|
sfx->registration_sequence = s_registration_sequence;
|
|
sfx->truename = s;
|
|
|
|
return sfx;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
S_BeginRegistration
|
|
|
|
=====================
|
|
*/
|
|
void S_BeginRegistration (void)
|
|
{
|
|
s_registration_sequence++;
|
|
s_registering = true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
S_RegisterSound
|
|
|
|
==================
|
|
*/
|
|
sfx_t *S_RegisterSound (char *name)
|
|
{
|
|
sfx_t *sfx;
|
|
|
|
if (!sound_started)
|
|
return NULL;
|
|
|
|
sfx = S_FindName (name, true);
|
|
sfx->registration_sequence = s_registration_sequence;
|
|
|
|
if (!s_registering)
|
|
S_LoadSound (sfx);
|
|
|
|
return sfx;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
S_EndRegistration
|
|
|
|
=====================
|
|
*/
|
|
void S_EndRegistration (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;
|
|
if (sfx->registration_sequence != s_registration_sequence)
|
|
{ // don't need this sound
|
|
if (sfx->cache) // it is possible to have a leftover
|
|
Z_Free (sfx->cache); // from a server that didn't finish loading
|
|
memset (sfx, 0, sizeof(*sfx));
|
|
}
|
|
else
|
|
{ // make sure it is paged in
|
|
if (sfx->cache)
|
|
{
|
|
size = sfx->cache->length*sfx->cache->width;
|
|
Com_PageInMemory ((byte *)sfx->cache, size);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// load everything in
|
|
for (i=0, sfx=known_sfx ; i < num_sfx ; i++,sfx++)
|
|
{
|
|
if (!sfx->name[0])
|
|
continue;
|
|
S_LoadSound (sfx);
|
|
}
|
|
|
|
s_registering = false;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
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
|
|
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_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->value)
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
sc = S_LoadSound (ch->sfx);
|
|
ch->end = paintedtime + sc->length;
|
|
|
|
// free the playsound
|
|
S_FreePlaysound (ps);
|
|
}
|
|
|
|
struct sfx_s *S_RegisterSexedSound (entity_state_t *ent, char *base)
|
|
{
|
|
int n;
|
|
char *p;
|
|
struct sfx_s *sfx;
|
|
QFile *f;
|
|
char model[MAX_QPATH];
|
|
char sexedFilename[MAX_QPATH];
|
|
char maleFilename[MAX_QPATH];
|
|
|
|
// determine what model the client is using
|
|
model[0] = 0;
|
|
n = CS_PLAYERSKINS + ent->number - 1;
|
|
if (cl.configstrings[n][0])
|
|
{
|
|
p = strchr(cl.configstrings[n], '\\');
|
|
if (p)
|
|
{
|
|
p += 1;
|
|
strcpy(model, p);
|
|
p = strchr(model, '/');
|
|
if (p)
|
|
*p = 0;
|
|
}
|
|
}
|
|
// if we can't figure it out, they're male
|
|
if (!model[0])
|
|
strcpy(model, "male");
|
|
|
|
// see if we already know of the model specific sound
|
|
Com_sprintf (sexedFilename, sizeof(sexedFilename), "#players/%s/%s", model, base+1);
|
|
sfx = S_FindName (sexedFilename, false);
|
|
|
|
if (!sfx)
|
|
{
|
|
// no, so see if it exists
|
|
FS_FOpenFile (&sexedFilename[1], &f);
|
|
if (f)
|
|
{
|
|
// yes, close the file and register it
|
|
FS_FCloseFile (f);
|
|
sfx = S_RegisterSound (sexedFilename);
|
|
}
|
|
else
|
|
{
|
|
// no, revert to the male sound in the pak0.pak
|
|
Com_sprintf (maleFilename, sizeof(maleFilename), "player/%s/%s", "male", base+1);
|
|
sfx = S_AliasName (sexedFilename, maleFilename);
|
|
}
|
|
}
|
|
|
|
return sfx;
|
|
}
|
|
|
|
|
|
// =======================================================================
|
|
// 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;
|
|
|
|
if (sfx->name[0] == '*')
|
|
sfx = S_RegisterSexedSound(&cl_entities[entnum].current, sfx->name);
|
|
|
|
// make sure the sound is loaded
|
|
sc = S_LoadSound (sfx);
|
|
if (!sc)
|
|
return; // couldn't load the sound's data
|
|
|
|
vol = fvol*255;
|
|
|
|
// make the playsound_t
|
|
ps = S_AllocPlaysound ();
|
|
if (!ps)
|
|
return;
|
|
|
|
if (origin)
|
|
{
|
|
VectorCopy (origin, ps->origin);
|
|
ps->fixed_origin = true;
|
|
}
|
|
else
|
|
ps->fixed_origin = false;
|
|
|
|
ps->entnum = entnum;
|
|
ps->entchannel = entchannel;
|
|
ps->attenuation = attenuation;
|
|
ps->volume = vol;
|
|
ps->sfx = sfx;
|
|
|
|
// drift s_beginofs
|
|
start = cl.frame.servertime * 0.001 * dma.speed + s_beginofs;
|
|
if (start < paintedtime)
|
|
{
|
|
start = paintedtime;
|
|
s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed);
|
|
}
|
|
else if (start > paintedtime + 0.3 * dma.speed)
|
|
{
|
|
start = paintedtime + 0.1 * dma.speed;
|
|
s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed);
|
|
}
|
|
else
|
|
{
|
|
s_beginofs-=10;
|
|
}
|
|
|
|
if (!timeofs)
|
|
ps->begin = paintedtime;
|
|
else
|
|
ps->begin = start + timeofs * dma.speed;
|
|
|
|
// sort into the pending sound list
|
|
for (sort = s_pendingplays.next ;
|
|
sort != &s_pendingplays && sort->begin < ps->begin ;
|
|
sort = sort->next)
|
|
;
|
|
|
|
ps->next = sort;
|
|
ps->prev = sort->prev;
|
|
|
|
ps->next->prev = ps;
|
|
ps->prev->next = ps;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
S_StartLocalSound
|
|
==================
|
|
*/
|
|
void S_StartLocalSound (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_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 sounds[MAX_EDICTS];
|
|
int left, right, left_total, right_total;
|
|
channel_t *ch;
|
|
sfx_t *sfx;
|
|
sfxcache_t *sc;
|
|
int num;
|
|
entity_state_t *ent;
|
|
|
|
if (cl_paused->value)
|
|
return;
|
|
|
|
if (cls.state != ca_active)
|
|
return;
|
|
|
|
if (!cl.sound_prepped)
|
|
return;
|
|
|
|
for (i=0 ; i<cl.frame.num_entities ; i++)
|
|
{
|
|
num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
|
|
ent = &cl_parse_entities[num];
|
|
sounds[i] = ent->sound;
|
|
}
|
|
|
|
for (i=0 ; i<cl.frame.num_entities ; i++)
|
|
{
|
|
if (!sounds[i])
|
|
continue;
|
|
|
|
sfx = cl.sound_precache[sounds[i]];
|
|
if (!sfx)
|
|
continue; // bad sound effect
|
|
sc = sfx->cache;
|
|
if (!sc)
|
|
continue;
|
|
|
|
num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1);
|
|
ent = &cl_parse_entities[num];
|
|
|
|
// find the total contribution of all sounds of this type
|
|
S_SpatializeOrigin (ent->origin, 255.0, SOUND_LOOPATTENUATE,
|
|
&left_total, &right_total);
|
|
for (j=i+1 ; j<cl.frame.num_entities ; j++)
|
|
{
|
|
if (sounds[j] != sounds[i])
|
|
continue;
|
|
sounds[j] = 0; // don't check this again later
|
|
|
|
num = (cl.frame.parse_entities + j)&(MAX_PARSE_ENTITIES-1);
|
|
ent = &cl_parse_entities[num];
|
|
|
|
S_SpatializeOrigin (ent->origin, 255.0, SOUND_LOOPATTENUATE,
|
|
&left, &right);
|
|
left_total += left;
|
|
right_total += right;
|
|
}
|
|
|
|
if (left_total == 0 && right_total == 0)
|
|
continue; // not audible
|
|
|
|
// 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 = true; // remove next frame
|
|
ch->sfx = sfx;
|
|
ch->pos = paintedtime % sc->length;
|
|
ch->end = paintedtime + sc->length - ch->pos;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
============
|
|
S_RawSamples
|
|
|
|
Cinematic streaming and voice over network
|
|
============
|
|
*/
|
|
void S_RawSamples (int samples, int rate, int width, int channels, byte *data)
|
|
{
|
|
int i;
|
|
int src, dst;
|
|
float scale;
|
|
|
|
if (!sound_started)
|
|
return;
|
|
|
|
if (s_rawend < paintedtime)
|
|
s_rawend = paintedtime;
|
|
scale = (float)rate / dma.speed;
|
|
|
|
//Com_Printf ("%i < %i < %i\n", soundtime, paintedtime, s_rawend);
|
|
if (channels == 2 && width == 2)
|
|
{
|
|
if (scale == 1.0)
|
|
{ // optimized case
|
|
for (i=0 ; i<samples ; i++)
|
|
{
|
|
dst = s_rawend&(MAX_RAW_SAMPLES-1);
|
|
s_rawend++;
|
|
s_rawsamples[dst].left =
|
|
LittleShort(((short *)data)[i*2]) << 8;
|
|
s_rawsamples[dst].right =
|
|
LittleShort(((short *)data)[i*2+1]) << 8;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i=0 ; ; i++)
|
|
{
|
|
src = i*scale;
|
|
if (src >= samples)
|
|
break;
|
|
dst = s_rawend&(MAX_RAW_SAMPLES-1);
|
|
s_rawend++;
|
|
s_rawsamples[dst].left =
|
|
LittleShort(((short *)data)[src*2]) << 8;
|
|
s_rawsamples[dst].right =
|
|
LittleShort(((short *)data)[src*2+1]) << 8;
|
|
}
|
|
}
|
|
}
|
|
else if (channels == 1 && width == 2)
|
|
{
|
|
for (i=0 ; ; i++)
|
|
{
|
|
src = i*scale;
|
|
if (src >= samples)
|
|
break;
|
|
dst = s_rawend&(MAX_RAW_SAMPLES-1);
|
|
s_rawend++;
|
|
s_rawsamples[dst].left =
|
|
LittleShort(((short *)data)[src]) << 8;
|
|
s_rawsamples[dst].right =
|
|
LittleShort(((short *)data)[src]) << 8;
|
|
}
|
|
}
|
|
else if (channels == 2 && width == 1)
|
|
{
|
|
for (i=0 ; ; i++)
|
|
{
|
|
src = i*scale;
|
|
if (src >= samples)
|
|
break;
|
|
dst = s_rawend&(MAX_RAW_SAMPLES-1);
|
|
s_rawend++;
|
|
s_rawsamples[dst].left =
|
|
((char *)data)[src*2] << 16;
|
|
s_rawsamples[dst].right =
|
|
((char *)data)[src*2+1] << 16;
|
|
}
|
|
}
|
|
else if (channels == 1 && width == 1)
|
|
{
|
|
for (i=0 ; ; i++)
|
|
{
|
|
src = i*scale;
|
|
if (src >= samples)
|
|
break;
|
|
dst = s_rawend&(MAX_RAW_SAMPLES-1);
|
|
s_rawend++;
|
|
s_rawsamples[dst].left =
|
|
(((byte *)data)[src]-128) << 16;
|
|
s_rawsamples[dst].right = (((byte *)data)[src]-128) << 16;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
============
|
|
S_Update
|
|
|
|
Called once each time through the main loop
|
|
============
|
|
*/
|
|
void S_Update(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up)
|
|
{
|
|
int i;
|
|
int total;
|
|
channel_t *ch;
|
|
channel_t *combine;
|
|
|
|
if (!sound_started)
|
|
return;
|
|
|
|
// if the laoding plaque is up, clear everything
|
|
// out to make sure we aren't looping a dirty
|
|
// dma buffer while loading
|
|
if (cls.disable_screen)
|
|
{
|
|
S_ClearBuffer ();
|
|
return;
|
|
}
|
|
|
|
// rebuild scale tables if volume is modified
|
|
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->value)
|
|
{
|
|
total = 0;
|
|
ch = channels;
|
|
for (i=0 ; i<MAX_CHANNELS; i++, ch++)
|
|
if (ch->sfx && (ch->leftvol || ch->rightvol) )
|
|
{
|
|
Com_Printf ("%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name);
|
|
total++;
|
|
}
|
|
|
|
Com_Printf ("----(%i)---- painted: %i\n", total, paintedtime);
|
|
}
|
|
|
|
// mix some sound
|
|
S_Update_();
|
|
}
|
|
|
|
void 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;
|
|
//endtime = (soundtime + 4096) & ~4095;
|
|
|
|
// mix to an even submission block size
|
|
endtime = (endtime + dma.submission_chunk-1)
|
|
& ~(dma.submission_chunk-1);
|
|
samps = dma.samples >> (dma.channels-1);
|
|
if (endtime - soundtime > samps)
|
|
endtime = soundtime + samps;
|
|
|
|
S_PaintChannels (endtime);
|
|
|
|
SNDDMA_Submit ();
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
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), '.'))
|
|
{
|
|
strcpy(name, Cmd_Argv(i));
|
|
strcat(name, ".wav");
|
|
}
|
|
else
|
|
strcpy(name, Cmd_Argv(i));
|
|
sfx = S_RegisterSound(name);
|
|
S_StartSound(NULL, cl.playernum+1, 0, sfx, 1.0, 1.0, 0);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
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->registration_sequence)
|
|
continue;
|
|
sc = sfx->cache;
|
|
if (sc)
|
|
{
|
|
size = sc->length*sc->width*(sc->stereo+1);
|
|
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);
|
|
}
|
|
|