ioq3/code/client/snd_dma.c

1626 lines
36 KiB
C
Raw Normal View History

2005-08-26 17:39:27 +00:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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 III Arena 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 III Arena source code; if not, write to the Free Software
2005-08-26 17:39:27 +00:00
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
/*****************************************************************************
* name: snd_dma.c
*
* desc: main control for any streaming sound output device
*
* $Archive: /MissionPack/code/client/snd_dma.c $
*
*****************************************************************************/
#include "snd_local.h"
#include "snd_codec.h"
2005-08-26 17:39:27 +00:00
#include "client.h"
2005-09-02 20:13:47 +00:00
void S_Update_( void );
void S_Base_StopAllSounds(void);
void S_Base_StopBackgroundTrack( void );
2005-08-26 17:39:27 +00:00
snd_stream_t *s_backgroundStream = NULL;
2005-08-26 17:39:27 +00:00
static char s_backgroundLoop[MAX_QPATH];
//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused
// =======================================================================
// Internal sound data & structures
// =======================================================================
// only begin attenuating sound volumes when outside the FULLVOLUME range
#define SOUND_FULLVOLUME 80
#define SOUND_ATTENUATE 0.0008f
channel_t s_channels[MAX_CHANNELS];
channel_t loop_channels[MAX_CHANNELS];
int numLoopChannels;
static int s_soundStarted;
static qboolean s_soundMuted;
dma_t dma;
static int listener_number;
static vec3_t listener_origin;
static vec3_t listener_axis[3];
int s_soundtime; // sample PAIRS
int s_paintedtime; // sample PAIRS
// MAX_SFX may be larger than MAX_SOUNDS because
// of custom player sounds
#define MAX_SFX 4096
sfx_t s_knownSfx[MAX_SFX];
int s_numSfx = 0;
#define LOOP_HASH 128
static sfx_t *sfxHash[LOOP_HASH];
cvar_t *s_testsound;
cvar_t *s_show;
cvar_t *s_mixahead;
cvar_t *s_mixPreStep;
static loopSound_t loopSounds[MAX_GENTITIES];
static channel_t *freelist = NULL;
int s_rawend[MAX_RAW_STREAMS];
portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES];
2005-08-26 17:39:27 +00:00
// ====================================================================
// User-setable variables
// ====================================================================
void S_Base_SoundInfo(void) {
2005-08-26 17:39:27 +00:00
Com_Printf("----- Sound Info -----\n" );
if (!s_soundStarted) {
Com_Printf ("sound system not started\n");
} else {
Com_Printf("%5d stereo\n", dma.channels - 1);
Com_Printf("%5d samples\n", dma.samples);
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("%p dma buffer\n", dma.buffer);
if ( s_backgroundStream ) {
2005-08-26 17:39:27 +00:00
Com_Printf("Background file: %s\n", s_backgroundLoop );
} else {
Com_Printf("No background file.\n" );
}
}
Com_Printf("----------------------\n" );
}
#ifdef USE_VOIP
static
void S_Base_StartCapture( void )
{
// !!! FIXME: write me.
}
static
int S_Base_AvailableCaptureSamples( void )
{
// !!! FIXME: write me.
return 0;
}
static
void S_Base_Capture( int samples, byte *data )
{
// !!! FIXME: write me.
}
static
void S_Base_StopCapture( void )
{
// !!! FIXME: write me.
}
static
void S_Base_MasterGain( float val )
{
// !!! FIXME: write me.
}
#endif
2005-08-26 17:39:27 +00:00
/*
=================
S_Base_SoundList
=================
2005-08-26 17:39:27 +00:00
*/
void S_Base_SoundList( void ) {
int i;
sfx_t *sfx;
int size, total;
char type[4][16];
char mem[2][16];
2005-08-26 17:39:27 +00:00
strcpy(type[0], "16bit");
strcpy(type[1], "adpcm");
strcpy(type[2], "daub4");
strcpy(type[3], "mulaw");
strcpy(mem[0], "paged out");
strcpy(mem[1], "resident ");
total = 0;
for (sfx=s_knownSfx, i=0 ; i<s_numSfx ; i++, sfx++) {
size = sfx->soundLength;
total += size;
Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod],
sfx->soundName, mem[sfx->inMemory] );
2005-08-26 17:39:27 +00:00
}
Com_Printf ("Total resident: %i\n", total);
S_DisplayFreeMemory();
2005-08-26 17:39:27 +00:00
}
2005-08-26 17:39:27 +00:00
void S_ChannelFree(channel_t *v) {
v->thesfx = NULL;
*(channel_t **)v = freelist;
freelist = (channel_t*)v;
}
2005-09-02 20:13:47 +00:00
channel_t* S_ChannelMalloc( void ) {
2005-08-26 17:39:27 +00:00
channel_t *v;
if (freelist == NULL) {
return NULL;
}
v = freelist;
freelist = *(channel_t **)freelist;
v->allocTime = Com_Milliseconds();
return v;
}
2005-09-02 20:13:47 +00:00
void S_ChannelSetup( void ) {
2005-08-26 17:39:27 +00:00
channel_t *p, *q;
// clear all the sounds so they don't
Com_Memset( s_channels, 0, sizeof( s_channels ) );
p = s_channels;;
q = p + MAX_CHANNELS;
while (--q > p) {
*(channel_t **)q = q-1;
}
*(channel_t **)q = NULL;
freelist = p + MAX_CHANNELS - 1;
Com_DPrintf("Channel memory manager started\n");
}
// =======================================================================
// Load a sound
// =======================================================================
/*
================
return a hash value for the sfx name
================
*/
static long S_HashSFXName(const char *name) {
int i;
long hash;
char letter;
hash = 0;
i = 0;
while (name[i] != '\0') {
letter = tolower(name[i]);
if (letter =='.') break; // don't include extension
if (letter =='\\') letter = '/'; // damn path names
hash+=(long)(letter)*(i+119);
i++;
}
hash &= (LOOP_HASH-1);
return hash;
}
/*
==================
S_FindName
Will allocate a new sfx if it isn't found
==================
*/
static sfx_t *S_FindName( const char *name ) {
int i;
int hash;
sfx_t *sfx;
if (!name) {
Com_Error(ERR_FATAL, "Sound name is NULL");
2005-08-26 17:39:27 +00:00
}
2005-08-26 17:39:27 +00:00
if (!name[0]) {
Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" );
return NULL;
2005-08-26 17:39:27 +00:00
}
if (strlen(name) >= MAX_QPATH) {
Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", name );
return NULL;
2005-08-26 17:39:27 +00:00
}
if (name[0] == '*') {
Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", name );
return NULL;
}
2005-08-26 17:39:27 +00:00
hash = S_HashSFXName(name);
sfx = sfxHash[hash];
// see if already loaded
while (sfx) {
if (!Q_stricmp(sfx->soundName, name) ) {
return sfx;
}
sfx = sfx->next;
}
// find a free sfx
for (i=0 ; i < s_numSfx ; i++) {
if (!s_knownSfx[i].soundName[0]) {
break;
}
}
if (i == s_numSfx) {
if (s_numSfx == MAX_SFX) {
Com_Error (ERR_FATAL, "S_FindName: out of sfx_t");
}
s_numSfx++;
}
sfx = &s_knownSfx[i];
Com_Memset (sfx, 0, sizeof(*sfx));
strcpy (sfx->soundName, name);
sfx->next = sfxHash[hash];
sfxHash[hash] = sfx;
return sfx;
}
/*
=================
S_DefaultSound
=================
*/
void S_DefaultSound( sfx_t *sfx ) {
int i;
sfx->soundLength = 512;
sfx->soundData = SND_malloc();
sfx->soundData->next = NULL;
for ( i = 0 ; i < sfx->soundLength ; i++ ) {
sfx->soundData->sndChunk[i] = i;
}
}
/*
===================
S_DisableSounds
Disables sounds until the next S_BeginRegistration.
This is called when the hunk is cleared and the sounds
are no longer valid.
===================
*/
void S_Base_DisableSounds( void ) {
S_Base_StopAllSounds();
2005-08-26 17:39:27 +00:00
s_soundMuted = qtrue;
}
/*
==================
S_RegisterSound
Creates a default buzz sound if the file can't be loaded
==================
*/
sfxHandle_t S_Base_RegisterSound( const char *name, qboolean compressed ) {
2005-08-26 17:39:27 +00:00
sfx_t *sfx;
compressed = qfalse;
if (!s_soundStarted) {
return 0;
}
sfx = S_FindName( name );
if ( !sfx ) {
2005-08-26 17:39:27 +00:00
return 0;
}
if ( sfx->soundData ) {
if ( sfx->defaultSound ) {
Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
return 0;
}
return sfx - s_knownSfx;
}
sfx->inMemory = qfalse;
sfx->soundCompressed = compressed;
S_memoryLoad(sfx);
if ( sfx->defaultSound ) {
Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
return 0;
}
return sfx - s_knownSfx;
}
/*
=====================
S_BeginRegistration
=====================
*/
void S_Base_BeginRegistration( void ) {
s_soundMuted = qfalse; // we can play again
if (s_numSfx == 0) {
SND_setup();
Com_Memset(s_knownSfx, '\0', sizeof(s_knownSfx));
Com_Memset(sfxHash, '\0', sizeof(sfx_t *) * LOOP_HASH);
S_Base_RegisterSound("sound/feedback/hit.wav", qfalse); // changed to a sound in baseq3
}
}
2005-08-26 17:39:27 +00:00
void S_memoryLoad(sfx_t *sfx) {
// load the sound file
if ( !S_LoadSound ( sfx ) ) {
// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName );
sfx->defaultSound = qtrue;
}
sfx->inMemory = qtrue;
}
//=============================================================================
/*
=================
S_SpatializeOrigin
Used for spatializing s_channels
=================
*/
void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol)
{
vec_t dot;
vec_t dist;
vec_t lscale, rscale, scale;
vec3_t source_vec;
vec3_t vec;
const float dist_mult = SOUND_ATTENUATE;
// 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
VectorRotate( source_vec, listener_axis, vec );
dot = -vec[1];
if (dma.channels == 1)
{ // no attenuation = no spatialization
rscale = 1.0;
lscale = 1.0;
}
else
{
rscale = 0.5 * (1.0 + dot);
lscale = 0.5 * (1.0 - dot);
if ( rscale < 0 ) {
rscale = 0;
}
if ( lscale < 0 ) {
lscale = 0;
}
}
// add in distance effect
scale = (1.0 - dist) * rscale;
*right_vol = (master_vol * scale);
if (*right_vol < 0)
*right_vol = 0;
scale = (1.0 - dist) * lscale;
*left_vol = (master_vol * scale);
if (*left_vol < 0)
*left_vol = 0;
}
// =======================================================================
// Start a sound effect
// =======================================================================
/*
=================
S_Base_HearingThroughEntity
Also see S_AL_HearingThroughEntity
=================
*/
static qboolean S_Base_HearingThroughEntity( int entityNum, vec3_t origin )
{
float distanceSq;
vec3_t sorigin;
if (origin)
VectorCopy(origin, sorigin);
else
VectorCopy(loopSounds[entityNum].origin, sorigin);
if( listener_number == entityNum )
{
2013-02-16 21:03:56 +00:00
// This is an outrageous hack to detect
// whether or not the player is rendering in third person or not. We can't
// ask the renderer because the renderer has no notion of entities and we
// can't ask cgame since that would involve changing the API and hence mod
// compatibility. I don't think there is any way around this, but I'll leave
// the FIXME just in case anyone has a bright idea.
distanceSq = DistanceSquared(
sorigin,
listener_origin );
if( distanceSq > THIRD_PERSON_THRESHOLD_SQ )
return qfalse; //we're the player, but third person
else
return qtrue; //we're the player
}
else
return qfalse; //not the player
}
2005-08-26 17:39:27 +00:00
/*
====================
S_Base_StartSoundEx
2005-08-26 17:39:27 +00:00
Validates the parms and ques the sound up
if origin is NULL, the sound will be dynamically sourced from the entity
2005-08-26 17:39:27 +00:00
Entchannel 0 will never override a playing sound
====================
*/
static void S_Base_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, qboolean localSound ) {
2005-08-26 17:39:27 +00:00
channel_t *ch;
sfx_t *sfx;
int i, oldest, chosen, time;
int inplay, allowed;
qboolean fullVolume;
2005-08-26 17:39:27 +00:00
if ( !s_soundStarted || s_soundMuted ) {
return;
}
2012-11-18 19:09:54 +00:00
if ( !origin && ( entityNum < 0 || entityNum >= MAX_GENTITIES ) ) {
2005-08-26 17:39:27 +00:00
Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum );
}
if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle );
2005-08-26 17:39:27 +00:00
return;
}
sfx = &s_knownSfx[ sfxHandle ];
if (sfx->inMemory == qfalse) {
S_memoryLoad(sfx);
}
if ( s_show->integer == 1 ) {
Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName );
}
time = Com_Milliseconds();
// Com_Printf("playing %s\n", sfx->soundName);
// pick a channel to play on
allowed = 4;
if (entityNum == listener_number) {
allowed = 8;
}
fullVolume = qfalse;
if (localSound || S_Base_HearingThroughEntity(entityNum, origin)) {
fullVolume = qtrue;
}
2005-08-26 17:39:27 +00:00
ch = s_channels;
inplay = 0;
for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) {
if (ch->entnum == entityNum && ch->thesfx == sfx) {
if (time - ch->allocTime < 50) {
2005-08-26 17:39:27 +00:00
// if (Cvar_VariableValue( "cg_showmiss" )) {
// Com_Printf("double sound start\n");
// }
return;
}
inplay++;
}
}
if (inplay>allowed) {
return;
}
sfx->lastTimeUsed = time;
ch = S_ChannelMalloc(); // entityNum, entchannel);
if (!ch) {
ch = s_channels;
oldest = sfx->lastTimeUsed;
chosen = -1;
for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
oldest = ch->allocTime;
chosen = i;
}
}
if (chosen == -1) {
ch = s_channels;
for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
if (ch->entnum != listener_number && ch->allocTime<oldest && ch->entchannel != CHAN_ANNOUNCER) {
oldest = ch->allocTime;
chosen = i;
}
}
if (chosen == -1) {
ch = s_channels;
2005-08-26 17:39:27 +00:00
if (ch->entnum == listener_number) {
for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
if (ch->allocTime<oldest) {
oldest = ch->allocTime;
chosen = i;
}
}
}
if (chosen == -1) {
Com_Printf("dropping sound\n");
return;
}
}
}
ch = &s_channels[chosen];
ch->allocTime = sfx->lastTimeUsed;
}
if (origin) {
VectorCopy (origin, ch->origin);
ch->fixed_origin = qtrue;
} else {
ch->fixed_origin = qfalse;
}
ch->master_vol = 127;
ch->entnum = entityNum;
ch->thesfx = sfx;
ch->startSample = START_SAMPLE_IMMEDIATE;
ch->entchannel = entchannel;
ch->leftvol = ch->master_vol; // these will get calced at next spatialize
ch->rightvol = ch->master_vol; // unless the game isn't running
ch->doppler = qfalse;
ch->fullVolume = fullVolume;
2005-08-26 17:39:27 +00:00
}
/*
====================
S_StartSound
if origin is NULL, the sound will be dynamically sourced from the entity
====================
*/
void S_Base_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) {
S_Base_StartSoundEx( origin, entityNum, entchannel, sfxHandle, qfalse );
}
2005-08-26 17:39:27 +00:00
/*
==================
S_StartLocalSound
==================
*/
void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) {
2005-08-26 17:39:27 +00:00
if ( !s_soundStarted || s_soundMuted ) {
return;
}
if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle );
2005-08-26 17:39:27 +00:00
return;
}
S_Base_StartSoundEx( NULL, listener_number, channelNum, sfxHandle, qtrue );
2005-08-26 17:39:27 +00:00
}
/*
==================
S_ClearSoundBuffer
If we are about to perform file access, clear the buffer
so sound doesn't stutter.
==================
*/
void S_Base_ClearSoundBuffer( void ) {
2005-08-26 17:39:27 +00:00
int clear;
if (!s_soundStarted)
return;
// stop looping sounds
Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t));
Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t));
numLoopChannels = 0;
S_ChannelSetup();
Com_Memset(s_rawend, '\0', sizeof (s_rawend));
2005-08-26 17:39:27 +00:00
if (dma.samplebits == 8)
clear = 0x80;
else
clear = 0;
SNDDMA_BeginPainting ();
if (dma.buffer)
Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8);
2005-08-26 17:39:27 +00:00
SNDDMA_Submit ();
}
/*
==================
S_StopAllSounds
==================
*/
void S_Base_StopAllSounds(void) {
2005-08-26 17:39:27 +00:00
if ( !s_soundStarted ) {
return;
}
// stop the background music
S_Base_StopBackgroundTrack();
2005-08-26 17:39:27 +00:00
S_Base_ClearSoundBuffer ();
2005-08-26 17:39:27 +00:00
}
/*
==============================================================
continuous looping sounds are added each frame
==============================================================
*/
void S_Base_StopLoopingSound(int entityNum) {
2005-08-26 17:39:27 +00:00
loopSounds[entityNum].active = qfalse;
// loopSounds[entityNum].sfx = 0;
loopSounds[entityNum].kill = qfalse;
}
/*
==================
S_ClearLoopingSounds
==================
*/
void S_Base_ClearLoopingSounds( qboolean killall ) {
2005-08-26 17:39:27 +00:00
int i;
for ( i = 0 ; i < MAX_GENTITIES ; i++) {
if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) {
S_Base_StopLoopingSound(i);
2005-08-26 17:39:27 +00:00
}
}
numLoopChannels = 0;
}
/*
==================
S_AddLoopingSound
Called during entity generation for a frame
Include velocity in case I get around to doing doppler...
==================
*/
void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
2005-08-26 17:39:27 +00:00
sfx_t *sfx;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle );
2005-08-26 17:39:27 +00:00
return;
}
sfx = &s_knownSfx[ sfxHandle ];
if (sfx->inMemory == qfalse) {
S_memoryLoad(sfx);
}
if ( !sfx->soundLength ) {
Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
}
VectorCopy( origin, loopSounds[entityNum].origin );
VectorCopy( velocity, loopSounds[entityNum].velocity );
loopSounds[entityNum].active = qtrue;
loopSounds[entityNum].kill = qtrue;
loopSounds[entityNum].doppler = qfalse;
loopSounds[entityNum].oldDopplerScale = 1.0;
loopSounds[entityNum].dopplerScale = 1.0;
loopSounds[entityNum].sfx = sfx;
if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) {
vec3_t out;
float lena, lenb;
loopSounds[entityNum].doppler = qtrue;
lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin);
VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out);
lenb = DistanceSquared(loopSounds[listener_number].origin, out);
if ((loopSounds[entityNum].framenum+1) != cls.framecount) {
loopSounds[entityNum].oldDopplerScale = 1.0;
} else {
loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale;
}
loopSounds[entityNum].dopplerScale = lenb/(lena*100);
if (loopSounds[entityNum].dopplerScale<=1.0) {
loopSounds[entityNum].doppler = qfalse; // don't bother doing the math
2005-09-29 22:53:31 +00:00
} else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) {
loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE;
}
2005-08-26 17:39:27 +00:00
}
loopSounds[entityNum].framenum = cls.framecount;
}
/*
==================
S_AddLoopingSound
Called during entity generation for a frame
Include velocity in case I get around to doing doppler...
==================
*/
void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) {
2005-08-26 17:39:27 +00:00
sfx_t *sfx;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle );
2005-08-26 17:39:27 +00:00
return;
}
sfx = &s_knownSfx[ sfxHandle ];
if (sfx->inMemory == qfalse) {
S_memoryLoad(sfx);
}
if ( !sfx->soundLength ) {
Com_Error( ERR_DROP, "%s has length 0", sfx->soundName );
}
VectorCopy( origin, loopSounds[entityNum].origin );
VectorCopy( velocity, loopSounds[entityNum].velocity );
loopSounds[entityNum].sfx = sfx;
loopSounds[entityNum].active = qtrue;
loopSounds[entityNum].kill = qfalse;
loopSounds[entityNum].doppler = qfalse;
}
/*
==================
S_AddLoopSounds
Spatialize all of the looping sounds.
All sounds are on the same cycle, so any duplicates can just
sum up the channel multipliers.
==================
*/
void S_AddLoopSounds (void) {
int i, j, time;
int left_total, right_total, left, right;
channel_t *ch;
loopSound_t *loop, *loop2;
static int loopFrame;
numLoopChannels = 0;
time = Com_Milliseconds();
loopFrame++;
for ( i = 0 ; i < MAX_GENTITIES ; i++) {
loop = &loopSounds[i];
if ( !loop->active || loop->mergeFrame == loopFrame ) {
continue; // already merged into an earlier sound
}
if (loop->kill) {
S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d
} else {
S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere
}
loop->sfx->lastTimeUsed = time;
for (j=(i+1); j< MAX_GENTITIES ; j++) {
loop2 = &loopSounds[j];
if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) {
continue;
}
loop2->mergeFrame = loopFrame;
if (loop2->kill) {
S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d
} else {
S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere
}
loop2->sfx->lastTimeUsed = time;
left_total += left;
right_total += right;
}
if (left_total == 0 && right_total == 0) {
continue; // not audible
}
// allocate a channel
ch = &loop_channels[numLoopChannels];
if (left_total > 255) {
left_total = 255;
}
if (right_total > 255) {
right_total = 255;
}
ch->master_vol = 127;
ch->leftvol = left_total;
ch->rightvol = right_total;
ch->thesfx = loop->sfx;
ch->doppler = loop->doppler;
ch->dopplerScale = loop->dopplerScale;
ch->oldDopplerScale = loop->oldDopplerScale;
ch->fullVolume = qfalse;
2005-08-26 17:39:27 +00:00
numLoopChannels++;
if (numLoopChannels == MAX_CHANNELS) {
return;
}
}
}
//=============================================================================
/*
=================
S_ByteSwapRawSamples
If raw data has been loaded in little endien binary form, this must be done.
If raw data was calculated, as with ADPCM, this should not be called.
=================
*/
void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
int i;
if ( width != 2 ) {
return;
}
if ( LittleShort( 256 ) == 256 ) {
return;
}
if ( s_channels == 2 ) {
samples <<= 1;
}
for ( i = 0 ; i < samples ; i++ ) {
((short *)data)[i] = LittleShort( ((short *)data)[i] );
}
}
/*
============
S_Base_RawSamples
2005-08-26 17:39:27 +00:00
Music streaming
============
*/
void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum)
{
2005-08-26 17:39:27 +00:00
int i;
int src, dst;
float scale;
int intVolumeLeft, intVolumeRight;
portable_samplepair_t *rawsamples;
2005-08-26 17:39:27 +00:00
if ( !s_soundStarted || s_soundMuted ) {
return;
}
if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) {
return;
}
rawsamples = s_rawsamples[stream];
if ( s_muted->integer ) {
intVolumeLeft = intVolumeRight = 0;
} else {
int leftvol, rightvol;
if ( entityNum >= 0 && entityNum < MAX_GENTITIES ) {
// support spatialized raw streams, e.g. for VoIP
S_SpatializeOrigin( loopSounds[ entityNum ].origin, 256, &leftvol, &rightvol );
} else {
leftvol = rightvol = 256;
}
intVolumeLeft = leftvol * volume * s_volume->value;
intVolumeRight = rightvol * volume * s_volume->value;
}
2005-08-26 17:39:27 +00:00
if ( s_rawend[stream] < s_soundtime ) {
Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime );
s_rawend[stream] = s_soundtime;
2005-08-26 17:39:27 +00:00
}
scale = (float)rate / dma.speed;
//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]);
2005-08-26 17:39:27 +00:00
if (s_channels == 2 && width == 2)
{
if (scale == 1.0)
{ // optimized case
for (i=0 ; i<samples ; i++)
{
dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
s_rawend[stream]++;
rawsamples[dst].left = ((short *)data)[i*2] * intVolumeLeft;
rawsamples[dst].right = ((short *)data)[i*2+1] * intVolumeRight;
2005-08-26 17:39:27 +00:00
}
}
else
{
for (i=0 ; ; i++)
{
src = i*scale;
if (src >= samples)
break;
dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
s_rawend[stream]++;
rawsamples[dst].left = ((short *)data)[src*2] * intVolumeLeft;
rawsamples[dst].right = ((short *)data)[src*2+1] * intVolumeRight;
2005-08-26 17:39:27 +00:00
}
}
}
else if (s_channels == 1 && width == 2)
{
for (i=0 ; ; i++)
{
src = i*scale;
if (src >= samples)
break;
dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
s_rawend[stream]++;
rawsamples[dst].left = ((short *)data)[src] * intVolumeLeft;
rawsamples[dst].right = ((short *)data)[src] * intVolumeRight;
2005-08-26 17:39:27 +00:00
}
}
else if (s_channels == 2 && width == 1)
{
intVolumeLeft *= 256;
intVolumeRight *= 256;
2005-08-26 17:39:27 +00:00
for (i=0 ; ; i++)
{
src = i*scale;
if (src >= samples)
break;
dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
s_rawend[stream]++;
rawsamples[dst].left = ((char *)data)[src*2] * intVolumeLeft;
rawsamples[dst].right = ((char *)data)[src*2+1] * intVolumeRight;
2005-08-26 17:39:27 +00:00
}
}
else if (s_channels == 1 && width == 1)
{
intVolumeLeft *= 256;
intVolumeRight *= 256;
2005-08-26 17:39:27 +00:00
for (i=0 ; ; i++)
{
src = i*scale;
if (src >= samples)
break;
dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1);
s_rawend[stream]++;
rawsamples[dst].left = (((byte *)data)[src]-128) * intVolumeLeft;
rawsamples[dst].right = (((byte *)data)[src]-128) * intVolumeRight;
2005-08-26 17:39:27 +00:00
}
}
if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) {
Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime );
2005-08-26 17:39:27 +00:00
}
}
//=============================================================================
/*
=====================
S_UpdateEntityPosition
let the sound system know where an entity currently is
======================
*/
void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) {
2012-11-18 19:09:54 +00:00
if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) {
2005-08-26 17:39:27 +00:00
Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
}
VectorCopy( origin, loopSounds[entityNum].origin );
}
/*
============
S_Respatialize
Change the volumes of all the playing sounds for changes in their positions
============
*/
void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) {
2005-08-26 17:39:27 +00:00
int i;
channel_t *ch;
vec3_t origin;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
listener_number = entityNum;
VectorCopy(head, listener_origin);
VectorCopy(axis[0], listener_axis[0]);
VectorCopy(axis[1], listener_axis[1]);
VectorCopy(axis[2], listener_axis[2]);
// update spatialization for dynamic sounds
ch = s_channels;
for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
if ( !ch->thesfx ) {
continue;
}
// local and first person sounds will always be full volume
if (ch->fullVolume) {
2005-08-26 17:39:27 +00:00
ch->leftvol = ch->master_vol;
ch->rightvol = ch->master_vol;
} else {
if (ch->fixed_origin) {
VectorCopy( ch->origin, origin );
} else {
VectorCopy( loopSounds[ ch->entnum ].origin, origin );
}
S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol);
}
}
// add loopsounds
S_AddLoopSounds ();
}
/*
========================
S_ScanChannelStarts
Returns qtrue if any new sounds were started since the last mix
========================
*/
qboolean S_ScanChannelStarts( void ) {
channel_t *ch;
int i;
qboolean newSamples;
newSamples = qfalse;
ch = s_channels;
for (i=0; i<MAX_CHANNELS ; i++, ch++) {
if ( !ch->thesfx ) {
continue;
}
// if this channel was just started this frame,
// set the sample count to it begins mixing
// into the very first sample
if ( ch->startSample == START_SAMPLE_IMMEDIATE ) {
ch->startSample = s_paintedtime;
newSamples = qtrue;
continue;
}
// if it is completely finished by now, clear it
if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) {
S_ChannelFree(ch);
}
}
return newSamples;
}
/*
============
S_Update
Called once each time through the main loop
============
*/
void S_Base_Update( void ) {
2005-08-26 17:39:27 +00:00
int i;
int total;
channel_t *ch;
if ( !s_soundStarted || s_soundMuted ) {
2005-11-06 16:24:49 +00:00
// Com_DPrintf ("not started or muted\n");
2005-08-26 17:39:27 +00:00
return;
}
//
// debugging output
//
if ( s_show->integer == 2 ) {
total = 0;
ch = s_channels;
for (i=0 ; i<MAX_CHANNELS; i++, ch++) {
if (ch->thesfx && (ch->leftvol || ch->rightvol) ) {
Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName);
2005-08-26 17:39:27 +00:00
total++;
}
}
Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime);
}
// add raw data from streamed samples
S_UpdateBackgroundTrack();
// mix some sound
S_Update_();
}
void S_GetSoundtime(void)
{
int samplepos;
static int buffers;
static int oldsamplepos;
int fullsamples;
fullsamples = dma.samples / dma.channels;
if( CL_VideoRecording( ) )
{
float fps = MIN(cl_aviFrameRate->value, 1000.0f);
float frameDuration = MAX(dma.speed / fps, 1.0f) + clc.aviSoundFrameRemainder;
int msec = (int)frameDuration;
s_soundtime += msec;
clc.aviSoundFrameRemainder = frameDuration - msec;
return;
}
2005-08-26 17:39:27 +00:00
// 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 (s_paintedtime > 0x40000000)
{ // time to chop things off to avoid 32 bit limits
buffers = 0;
s_paintedtime = fullsamples;
S_Base_StopAllSounds ();
2005-08-26 17:39:27 +00:00
}
}
oldsamplepos = samplepos;
s_soundtime = buffers*fullsamples + samplepos/dma.channels;
#if 0
// check to make sure that we haven't overshot
if (s_paintedtime < s_soundtime)
{
Com_DPrintf ("S_Update_ : overflow\n");
s_paintedtime = s_soundtime;
}
#endif
if ( dma.submission_chunk < 256 ) {
s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed;
} else {
s_paintedtime = s_soundtime + dma.submission_chunk;
}
}
void S_Update_(void) {
unsigned endtime;
int samps;
static float lastTime = 0.0f;
float ma, op;
float thisTime, sane;
static int ot = -1;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
thisTime = Com_Milliseconds();
// Updates s_soundtime
S_GetSoundtime();
if (s_soundtime == ot) {
return;
}
ot = s_soundtime;
// clear any sound effects that end before the current time,
// and start any new sounds
S_ScanChannelStarts();
sane = thisTime - lastTime;
if (sane<11) {
sane = 11; // 85hz
}
ma = s_mixahead->value * dma.speed;
op = s_mixPreStep->value + sane*dma.speed*0.01;
if (op < ma) {
ma = op;
}
// mix ahead of current position
endtime = s_soundtime + ma;
// mix to an even submission block size
endtime = (endtime + dma.submission_chunk-1)
& ~(dma.submission_chunk-1);
// never mix more than the complete buffer
samps = dma.samples >> (dma.channels-1);
if (endtime - s_soundtime > samps)
endtime = s_soundtime + samps;
SNDDMA_BeginPainting ();
S_PaintChannels (endtime);
SNDDMA_Submit ();
lastTime = thisTime;
}
/*
===============================================================================
background music functions
===============================================================================
*/
/*
======================
S_StopBackgroundTrack
======================
*/
void S_Base_StopBackgroundTrack( void ) {
if(!s_backgroundStream)
2005-08-26 17:39:27 +00:00
return;
S_CodecCloseStream(s_backgroundStream);
s_backgroundStream = NULL;
s_rawend[0] = 0;
2005-08-26 17:39:27 +00:00
}
/*
======================
S_OpenBackgroundStream
======================
*/
static void S_OpenBackgroundStream( const char *filename ) {
// close the background track, but DON'T reset s_rawend
// if restarting the same back ground track
if(s_backgroundStream)
{
S_CodecCloseStream(s_backgroundStream);
s_backgroundStream = NULL;
}
// Open stream
s_backgroundStream = S_CodecOpenStream(filename);
if(!s_backgroundStream) {
Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", filename );
return;
}
if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) {
Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", filename );
}
}
2005-08-26 17:39:27 +00:00
/*
======================
S_StartBackgroundTrack
======================
*/
void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){
2005-08-26 17:39:27 +00:00
if ( !intro ) {
intro = "";
}
if ( !loop || !loop[0] ) {
loop = intro;
}
Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop );
if(!*intro)
{
S_Base_StopBackgroundTrack();
2005-08-26 17:39:27 +00:00
return;
}
Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
2005-08-26 17:39:27 +00:00
S_OpenBackgroundStream( intro );
2005-08-26 17:39:27 +00:00
}
/*
======================
S_UpdateBackgroundTrack
======================
*/
void S_UpdateBackgroundTrack( void ) {
int bufferSamples;
int fileSamples;
byte raw[30000]; // just enough to fit in a mac stack frame
int fileBytes;
int r;
if(!s_backgroundStream) {
2005-08-26 17:39:27 +00:00
return;
}
// don't bother playing anything if musicvolume is 0
if ( s_musicVolume->value <= 0 ) {
2005-08-26 17:39:27 +00:00
return;
}
// see how many samples should be copied into the raw buffer
if ( s_rawend[0] < s_soundtime ) {
s_rawend[0] = s_soundtime;
2005-08-26 17:39:27 +00:00
}
while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) {
bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime);
2005-08-26 17:39:27 +00:00
// decide how much data needs to be read from the file
fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed;
2005-08-26 17:39:27 +00:00
if (!fileSamples)
return;
2005-08-26 17:39:27 +00:00
// our max buffer size
fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels);
2005-08-26 17:39:27 +00:00
if ( fileBytes > sizeof(raw) ) {
fileBytes = sizeof(raw);
fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
2005-08-26 17:39:27 +00:00
}
// Read
r = S_CodecReadStream(s_backgroundStream, fileBytes, raw);
if(r < fileBytes)
{
fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
2005-08-26 17:39:27 +00:00
}
if(r > 0)
{
// add to raw buffer
S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate,
s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1);
}
else
{
2005-08-26 17:39:27 +00:00
// loop
if(s_backgroundLoop[0])
{
S_OpenBackgroundStream( s_backgroundLoop );
if(!s_backgroundStream)
return;
}
else
{
S_Base_StopBackgroundTrack();
2005-08-26 17:39:27 +00:00
return;
}
}
2005-08-26 17:39:27 +00:00
}
}
/*
======================
S_FreeOldestSound
======================
*/
2005-09-23 17:39:14 +00:00
void S_FreeOldestSound( void ) {
2005-08-26 17:39:27 +00:00
int i, oldest, used;
sfx_t *sfx;
sndBuffer *buffer, *nbuffer;
oldest = Com_Milliseconds();
used = 0;
for (i=1 ; i < s_numSfx ; i++) {
sfx = &s_knownSfx[i];
if (sfx->inMemory && sfx->lastTimeUsed<oldest) {
used = i;
oldest = sfx->lastTimeUsed;
}
}
sfx = &s_knownSfx[used];
Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName);
buffer = sfx->soundData;
while(buffer != NULL) {
nbuffer = buffer->next;
SND_free(buffer);
buffer = nbuffer;
}
sfx->inMemory = qfalse;
sfx->soundData = NULL;
}
// =======================================================================
// Shutdown sound engine
// =======================================================================
void S_Base_Shutdown( void ) {
if ( !s_soundStarted ) {
return;
}
SNDDMA_Shutdown();
SND_shutdown();
s_soundStarted = 0;
s_numSfx = 0;
Cmd_RemoveCommand("s_info");
}
/*
================
S_Init
================
*/
qboolean S_Base_Init( soundInterface_t *si ) {
qboolean r;
if( !si ) {
return qfalse;
}
s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE);
s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE);
s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT);
s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT);
r = SNDDMA_Init();
if ( r ) {
s_soundStarted = 1;
s_soundMuted = 1;
// s_numSfx = 0;
Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH);
s_soundtime = 0;
s_paintedtime = 0;
S_Base_StopAllSounds( );
} else {
return qfalse;
}
si->Shutdown = S_Base_Shutdown;
si->StartSound = S_Base_StartSound;
si->StartLocalSound = S_Base_StartLocalSound;
si->StartBackgroundTrack = S_Base_StartBackgroundTrack;
si->StopBackgroundTrack = S_Base_StopBackgroundTrack;
si->RawSamples = S_Base_RawSamples;
si->StopAllSounds = S_Base_StopAllSounds;
si->ClearLoopingSounds = S_Base_ClearLoopingSounds;
si->AddLoopingSound = S_Base_AddLoopingSound;
si->AddRealLoopingSound = S_Base_AddRealLoopingSound;
si->StopLoopingSound = S_Base_StopLoopingSound;
si->Respatialize = S_Base_Respatialize;
si->UpdateEntityPosition = S_Base_UpdateEntityPosition;
si->Update = S_Base_Update;
si->DisableSounds = S_Base_DisableSounds;
si->BeginRegistration = S_Base_BeginRegistration;
si->RegisterSound = S_Base_RegisterSound;
si->ClearSoundBuffer = S_Base_ClearSoundBuffer;
si->SoundInfo = S_Base_SoundInfo;
si->SoundList = S_Base_SoundList;
#ifdef USE_VOIP
si->StartCapture = S_Base_StartCapture;
si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples;
si->Capture = S_Base_Capture;
si->StopCapture = S_Base_StopCapture;
si->MasterGain = S_Base_MasterGain;
#endif
return qtrue;
}