mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
add4c7cc46
Added new SCR_DrawPic() variants in cl_screen.c. Added new graphics for text fields and sliders in menus. Improved mouse interaction for menu sliders. Added resettargets developer command to default Lazarus and missionpack DLLs. Added hint_test developer command to missionpack DLL. Fixed freeze developer command in default Lazarus and missionpack DLLs so it can be used more than once. More tweaks to Tactician Gunner prox mine safety checks in misssionpack DLL.
1335 lines
28 KiB
C
1335 lines
28 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1997-2001 Id Software, Inc.
|
|
|
|
This file is part of Quake 2 source code.
|
|
|
|
Quake 2 source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake 2 source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Quake 2 source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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;
|
|
cvar_t *s_usedefaultsound; // Knightmare added
|
|
#ifdef OGG_SUPPORT
|
|
cvar_t *s_musicvolume;
|
|
#endif
|
|
|
|
|
|
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);
|
|
Cvar_SetDescription ("s_initsound", "Enables the initialization of the sound subsystem.");
|
|
if (!cv->value)
|
|
Com_Printf ("not initializing.\n");
|
|
else
|
|
{
|
|
s_volume = Cvar_Get ("s_volume", "1.0", CVAR_ARCHIVE);
|
|
Cvar_SetDescription ("s_volume", "Sets the sound volume (normalized). 0 = mute, 1.0 = max.");
|
|
s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE);
|
|
Cvar_SetDescription ("s_khz", "Sets the sound sampling rate in KHz.");
|
|
s_loadas8bit = Cvar_Get ("s_loadas8bit", "0", CVAR_ARCHIVE);
|
|
Cvar_SetDescription ("s_loadas8bit", "Forces 8-bit low quality sounds when set to 1.");
|
|
s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE);
|
|
Cvar_SetDescription ("s_mixahead", "Sets sound mix-ahead time in seconds.");
|
|
s_show = Cvar_Get ("s_show", "0", 0);
|
|
Cvar_SetDescription ("s_show", "Enables sound debugging output.");
|
|
s_testsound = Cvar_Get ("s_testsound", "0", 0);
|
|
Cvar_SetDescription ("s_testsound", "Enables generated test sound.");
|
|
s_primary = Cvar_Get ("s_primary", "0", CVAR_ARCHIVE); // win32 specific
|
|
Cvar_SetDescription ("s_primary", "Enables primary sound buffer. Set to 1 for better performance, or 0 for improved compatibility.");
|
|
s_usedefaultsound = Cvar_Get ("s_usedefaultsound", "0", CVAR_ARCHIVE); // Knightmare added
|
|
Cvar_SetDescription ("s_usedefaultsound", "Enables use of a default generated tone when a sound file can't be loaded.");
|
|
#ifdef OGG_SUPPORT
|
|
s_musicvolume = Cvar_Get ("s_musicvolume", "1.0", CVAR_ARCHIVE); // Q2E
|
|
Cvar_SetDescription ("s_musicvolume", "Sets the music volume (normalized). 0 = mute, 1.0 = max.");
|
|
#endif
|
|
|
|
Cmd_AddCommand("play", S_Play);
|
|
Cmd_AddCommand("stopsound", S_StopAllSounds);
|
|
Cmd_AddCommand("soundlist", S_SoundList);
|
|
Cmd_AddCommand("soundinfo", S_SoundInfo_f);
|
|
#ifdef OGG_SUPPORT
|
|
Cmd_AddCommand("ogg_restart", S_OGG_Restart);
|
|
#endif
|
|
|
|
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 ();
|
|
}
|
|
|
|
#ifdef OGG_SUPPORT
|
|
S_OGG_Init();
|
|
#endif
|
|
|
|
Com_Printf("------------------------------------\n");
|
|
}
|
|
|
|
|
|
// =======================================================================
|
|
// Shutdown sound engine
|
|
// =======================================================================
|
|
|
|
void S_Shutdown (void)
|
|
{
|
|
int i;
|
|
sfx_t *sfx;
|
|
|
|
if (!sound_started)
|
|
return;
|
|
|
|
#ifdef OGG_SUPPORT
|
|
S_OGG_Shutdown();
|
|
#endif
|
|
|
|
SNDDMA_Shutdown();
|
|
|
|
sound_started = 0;
|
|
|
|
Cmd_RemoveCommand("play");
|
|
Cmd_RemoveCommand("stopsound");
|
|
Cmd_RemoveCommand("soundlist");
|
|
Cmd_RemoveCommand("soundinfo");
|
|
#ifdef OGG_SUPPORT
|
|
Cmd_RemoveCommand("ogg_restart");
|
|
#endif
|
|
|
|
// 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));
|
|
// strncpy (sfx->name, name);
|
|
Q_strncpyz (sfx->name, sizeof(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);
|
|
// strncpy (s, truename);
|
|
Q_strncpyz (s, MAX_QPATH, 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));
|
|
// strncpy (sfx->name, aliasname);
|
|
Q_strncpyz (sfx->name, sizeof(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;
|
|
|
|
// Knightmare added
|
|
// S_EndRegistration is only called while the refresh is prepped
|
|
// during a sound resart, so we can use this to restart the music track
|
|
if (cls.state == ca_active && cl.refresh_prepped && !CDAudio_Active())
|
|
CL_PlayBackgroundTrack ();
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
=================
|
|
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++)
|
|
{
|
|
#ifdef OGG_SUPPORT
|
|
// Don't let game sounds override streaming sounds
|
|
if (channels[ch_idx].streaming) // Q2E
|
|
continue;
|
|
#endif
|
|
|
|
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)
|
|
if (!ch || !ps->sfx || !ps->sfx->name[0]) // pointer / buffer check
|
|
{
|
|
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; // crashes here
|
|
|
|
// 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;
|
|
fileHandle_t f;
|
|
char model[MAX_QPATH];
|
|
char sexedFilename[MAX_QPATH];
|
|
char maleFilename[MAX_QPATH];
|
|
|
|
// determine what model the client is using
|
|
model[0] = 0;
|
|
|
|
// Knightmare- BIG UGLY HACK for old connected to server using old protocol
|
|
// Changed config strings require different parsing
|
|
if ( LegacyProtocol() )
|
|
n = OLD_CS_PLAYERSKINS + ent->number - 1;
|
|
else
|
|
n = CS_PLAYERSKINS + ent->number - 1;
|
|
|
|
if (cl.configstrings[n][0])
|
|
{
|
|
p = strchr(cl.configstrings[n], '\\');
|
|
if (p)
|
|
{
|
|
p += 1;
|
|
// strncpy(model, p);
|
|
Q_strncpyz (model, sizeof(model), p);
|
|
p = strchr(model, '/');
|
|
if (p)
|
|
*p = 0;
|
|
}
|
|
}
|
|
// if we can't figure it out, they're male
|
|
if (!model[0])
|
|
// strncpy(model, "male");
|
|
Q_strncpyz (model, sizeof(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, FS_READ);
|
|
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));
|
|
|
|
#ifdef OGG_SUPPORT
|
|
// Stop background track
|
|
S_StopBackgroundTrack (); // Q2E
|
|
#endif
|
|
|
|
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;
|
|
float dist_mult;
|
|
vec3_t origin_v; // Knightmare added
|
|
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];
|
|
|
|
#ifdef NEW_ENTITY_STATE_MEMBERS
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
if (ent->attenuation <= 0.0f || ent->attenuation == ATTN_STATIC)
|
|
dist_mult = SOUND_LOOPATTENUATE;
|
|
else
|
|
dist_mult = ent->attenuation * 0.0005;
|
|
#else
|
|
dist_mult = SOUND_LOOPATTENUATE;
|
|
#endif
|
|
#else
|
|
dist_mult = SOUND_LOOPATTENUATE;
|
|
#endif
|
|
// Knightmare- find correct origin for bmodels without origin brushes
|
|
if (ent->solid == 31) // special value for bmodels
|
|
{
|
|
cmodel_t *cmodel;
|
|
qboolean outofbounds = false;
|
|
int k;
|
|
|
|
cmodel = cl.model_clip[ent->modelindex];
|
|
if (cmodel) {
|
|
for (k=0; k<3; k++)
|
|
if (ent->origin[k] < cmodel->mins[k] || ent->origin[k] > cmodel->maxs[k])
|
|
outofbounds = true;
|
|
}
|
|
if (cmodel && outofbounds) {
|
|
for (k=0; k<3; k++)
|
|
origin_v[k] = ent->origin[k] + 0.5 * (cmodel->mins[k] + cmodel->maxs[k]);
|
|
}
|
|
else
|
|
VectorCopy (ent->origin, origin_v);
|
|
}
|
|
else
|
|
VectorCopy (ent->origin, origin_v);
|
|
|
|
// find the total contribution of all sounds of this type
|
|
S_SpatializeOrigin (origin_v, 255.0, dist_mult, // SOUND_LOOPATTENUATE // was ent->origin
|
|
&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 (origin_v, 255.0, dist_mult, // SOUND_LOOPATTENUATE // was ent->origin
|
|
&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, qboolean music)
|
|
{
|
|
int i;
|
|
int src, dst;
|
|
float scale;
|
|
int snd_vol;
|
|
|
|
if (!sound_started)
|
|
return;
|
|
|
|
#ifdef OGG_SUPPORT
|
|
if (music)
|
|
snd_vol = (int)(s_musicvolume->value * 256);
|
|
else
|
|
#endif
|
|
snd_vol = (int)(s_volume->value * 256);
|
|
|
|
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]) * snd_vol; // << 8;
|
|
s_rawsamples[dst].right =
|
|
LittleShort(((short *)data)[i*2+1]) * snd_vol; // << 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]) * snd_vol; // << 8;
|
|
s_rawsamples[dst].right =
|
|
LittleShort(((short *)data)[src*2+1]) * snd_vol; // << 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]) * snd_vol; // << 8;
|
|
s_rawsamples[dst].right =
|
|
LittleShort(((short *)data)[src]) * snd_vol; // << 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] << 8 ) * snd_vol; // << 16;
|
|
s_rawsamples[dst].right =
|
|
( ((char *)data)[src*2+1] << 8 ) * snd_vol; // << 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) << 8 ) * snd_vol; // << 16;
|
|
s_rawsamples[dst].right = ( (((byte *)data)[src]-128) << 8 ) * snd_vol; // << 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 ();
|
|
|
|
//if (s_musicvolume->modified)
|
|
// S_InitMusicScaletable ();
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
#ifdef OGG_SUPPORT
|
|
S_UpdateBackgroundTrack ();
|
|
#endif
|
|
|
|
// 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), '.'))
|
|
{
|
|
// strncpy(name, Cmd_Argv(i));
|
|
// strncat(name, ".wav");
|
|
Q_strncpyz (name, sizeof(name), Cmd_Argv(i));
|
|
Q_strncatz (name, sizeof(name), ".wav");
|
|
}
|
|
else
|
|
// strncpy(name, Cmd_Argv(i));
|
|
Q_strncpyz (name, sizeof(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);
|
|
}
|
|
|