q3rally/engine/code/client/snd_dma.c
zturtleman 96a9e2a9aa ioquake3 resync to revision 3511 from 3444.
This updates from SDL 2.0.4 to SDL 2.0.8.

Fix nullptr dereference in front of nullptr check in FS_CheckPak0
Fix undefined behaviour due to shifting signed in snd_mem.c
Fix shifting bits out of byte in tr_font.c
Fix shift into sign in cl_cin.c
Fix signed bit operations in MSG_ReadBits
Add missing address operator in cm_polylib.c
OpenGL1: Decay float[8] to float * in tr_marks.c
Avoid srcList[-1] in snd_openal.c
Fix the behaviour of CVAR_LATCH|CVAR_CHEAT cvars
Maximize cURL buffer size
Fix mouse grab after toggling fullscreen
Fix q3history buffer not cleared between mods and OOB-access
Revert "Removed "Color Depth" from q3_ui system settings, it didn't control anything."
Fix displayed color/depth/stencil bits values
Restore setting r_colorbits in q3_ui
Make setting r_stencilbits more consistent in Team Arena UI
Fix map list in Team Arena start server menu after entering SP menu
Support SDL audio devices that require float32 samples.
sdl_snd.c should just initialize SDL audio without checking SDL_WasInit().
There's no need to SDL_PauseAudio(1) before calling SDL_CloseAudio().
Added audio capture support to SDL backend.
Use the SDL2 audio device interface instead of the legacy 1.2 API.
Disable SDL audio capture until prebuilt SDL libraries are updated to 2.0.8.
Update SDL2 to 2.0.8
Add SDL 2.0.1 headers for macOS PPC
Make macOS Universal Bundle target 10.6 for x86 and x86_64
Fix possible bot goal state NULL pointer dereference
Fix uninitialized bot_goal_t fields
Remove unnecessary NULL pointer check in Cmd_RemoveCommand
Make UI_DrawProportionalString handle NULL string
Fix compiling against macOS system OpenAL and SDL2 frameworks
Fix array index in CanDamage() function - discovered by MARTY
Fix compiling Makefile (broke in macOS frameworks commit)
Fix clearing keys for control in Team Arena UI
Make s_useOpenAL be CVAR_LATCH
Improvements for dedicated camera followers (team follow1/2)
Fix not closing description.txt and fix path seperator
Fix duplicate bots displayed in Team Arena ingame add bot menu
OpenGL2: Fix parsing specularScale in shaders
Don't allow SDL audio capture using pulseaudio
Isolate the Altivec code so non-Altivec PPC targets can use the same binary.
Limit -maltivec to specific source files on OpenBSD too (untested)
Use SDL 2.0.1 headers for macOS ppc64
Fix console offset while Team Arena voiceMenu is open
OpenGL2: Readd r_deluxeSpecular.
Fix client kicked as unpure when missing the latest cgame/ui pk3s
Don't create multiple windows when GL context creation fails
Require OpenGL 1.2 for GL_CLAMP_TO_EDGE
Fix Linux uninstaller requiring Bash
Fix Linux uninstaller redirecting stderr to stdout in preuninstall.sh
Reported by @illwieckz.
Fix in_restart causing fatal error while video is shutdown
Allow pkg-config binary to be overridden with PKG_CONFIG
Make testgun command without argument disable test gun model
Remove unused renderer_buffer variable
Don't upload 8 bit grayscale images as 16 bit luminance
OpenGL1: Use RE_UploadCinematic() instead of duplicate code
Don't load non-core GL functions for OpenGL 3.2 core context
Load OpenGL ES 2.0 function procs
Don't check fixed function GL extensions when using shader pipeline
OpenGL2: Fix world VAO cache drawing when glIndex_t is unsigned short
OpenGL2: Misc fixes and cleanup
Fix IQM root joint backlerp when joint number is more than 0
Improve IQM loading
Improve IQM CPU vertex skinning performance
OpenGL2: Add GPU vertex skinning for IQM models
2018-07-30 11:35:12 +00:00

1624 lines
36 KiB
C

/*
===========================================================================
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
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"
#include "client.h"
void S_Update_( void );
void S_Base_StopAllSounds(void);
void S_Base_StopBackgroundTrack( void );
snd_stream_t *s_backgroundStream = NULL;
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];
// ====================================================================
// User-setable variables
// ====================================================================
void S_Base_SoundInfo(void) {
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 (%s)\n", dma.samplebits, dma.isfloat ? "float" : "int");
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 ) {
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 )
{
SNDDMA_StartCapture();
}
static
int S_Base_AvailableCaptureSamples( void )
{
return SNDDMA_AvailableCaptureSamples();
}
static
void S_Base_Capture( int samples, byte *data )
{
SNDDMA_Capture(samples, data);
}
static
void S_Base_StopCapture( void )
{
SNDDMA_StopCapture();
}
static
void S_Base_MasterGain( float val )
{
SNDDMA_MasterGain(val);
}
#endif
/*
=================
S_Base_SoundList
=================
*/
void S_Base_SoundList( void ) {
int i;
sfx_t *sfx;
int size, total;
char type[4][16];
char mem[2][16];
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] );
}
Com_Printf ("Total resident: %i\n", total);
S_DisplayFreeMemory();
}
void S_ChannelFree(channel_t *v) {
v->thesfx = NULL;
*(channel_t **)v = freelist;
freelist = (channel_t*)v;
}
channel_t* S_ChannelMalloc( void ) {
channel_t *v;
if (freelist == NULL) {
return NULL;
}
v = freelist;
freelist = *(channel_t **)freelist;
v->allocTime = Com_Milliseconds();
return v;
}
void S_ChannelSetup( void ) {
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");
}
if (!name[0]) {
Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" );
return NULL;
}
if (strlen(name) >= MAX_QPATH) {
Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", name );
return NULL;
}
if (name[0] == '*') {
Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", name );
return NULL;
}
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();
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 ) {
sfx_t *sfx;
compressed = qfalse;
if (!s_soundStarted) {
return 0;
}
sfx = S_FindName( name );
if ( !sfx ) {
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/misc/silence.wav", qfalse);
}
}
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 separation 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 )
{
// 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
}
/*
====================
S_Base_StartSoundEx
Validates the parms and ques the sound up
if origin is NULL, the sound will be dynamically sourced from the entity
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 ) {
channel_t *ch;
sfx_t *sfx;
int i, oldest, chosen, time;
int inplay, allowed;
qboolean fullVolume;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
if ( !origin && ( entityNum < 0 || entityNum >= MAX_GENTITIES ) ) {
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 );
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;
}
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) {
// 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;
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;
}
/*
====================
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 );
}
/*
==================
S_StartLocalSound
==================
*/
void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) {
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 );
return;
}
S_Base_StartSoundEx( NULL, listener_number, channelNum, sfxHandle, qtrue );
}
/*
==================
S_ClearSoundBuffer
If we are about to perform file access, clear the buffer
so sound doesn't stutter.
==================
*/
void S_Base_ClearSoundBuffer( void ) {
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));
if (dma.samplebits == 8)
clear = 0x80;
else
clear = 0;
SNDDMA_BeginPainting ();
if (dma.buffer)
Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8);
SNDDMA_Submit ();
}
/*
==================
S_StopAllSounds
==================
*/
void S_Base_StopAllSounds(void) {
if ( !s_soundStarted ) {
return;
}
// stop the background music
S_Base_StopBackgroundTrack();
S_Base_ClearSoundBuffer ();
}
/*
==============================================================
continuous looping sounds are added each frame
==============================================================
*/
void S_Base_StopLoopingSound(int entityNum) {
loopSounds[entityNum].active = qfalse;
// loopSounds[entityNum].sfx = 0;
loopSounds[entityNum].kill = qfalse;
}
/*
==================
S_ClearLoopingSounds
==================
*/
void S_Base_ClearLoopingSounds( qboolean killall ) {
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);
}
}
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 ) {
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 );
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
} else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) {
loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE;
}
}
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 ) {
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 );
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;
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
Music streaming
============
*/
void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum)
{
int i;
int src, dst;
float scale;
int intVolumeLeft, intVolumeRight;
portable_samplepair_t *rawsamples;
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;
}
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;
}
scale = (float)rate / dma.speed;
//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]);
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;
}
}
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;
}
}
}
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;
}
}
else if (s_channels == 2 && width == 1)
{
intVolumeLeft *= 256;
intVolumeRight *= 256;
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;
}
}
else if (s_channels == 1 && width == 1)
{
intVolumeLeft *= 256;
intVolumeRight *= 256;
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;
}
}
if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) {
Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime );
}
}
//=============================================================================
/*
=====================
S_UpdateEntityPosition
let the sound system know where an entity currently is
======================
*/
void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) {
if ( entityNum < 0 || entityNum >= MAX_GENTITIES ) {
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 ) {
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) {
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 ) {
int i;
int total;
channel_t *ch;
if ( !s_soundStarted || s_soundMuted ) {
// Com_DPrintf ("not started or muted\n");
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);
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;
}
// 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 ();
}
}
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)
return;
S_CodecCloseStream(s_backgroundStream);
s_backgroundStream = NULL;
s_rawend[0] = 0;
}
/*
======================
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 );
}
}
/*
======================
S_StartBackgroundTrack
======================
*/
void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){
if ( !intro ) {
intro = "";
}
if ( !loop || !loop[0] ) {
loop = intro;
}
Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop );
if(!*intro)
{
S_Base_StopBackgroundTrack();
return;
}
Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
S_OpenBackgroundStream( intro );
}
/*
======================
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) {
return;
}
// don't bother playing anything if musicvolume is 0
if ( s_musicVolume->value <= 0 ) {
return;
}
// see how many samples should be copied into the raw buffer
if ( s_rawend[0] < s_soundtime ) {
s_rawend[0] = s_soundtime;
}
while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) {
bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime);
// decide how much data needs to be read from the file
fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed;
if (!fileSamples)
return;
// our max buffer size
fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels);
if ( fileBytes > sizeof(raw) ) {
fileBytes = sizeof(raw);
fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
}
// Read
r = S_CodecReadStream(s_backgroundStream, fileBytes, raw);
if(r < fileBytes)
{
fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels);
}
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
{
// loop
if(s_backgroundLoop[0])
{
S_OpenBackgroundStream( s_backgroundLoop );
if(!s_backgroundStream)
return;
}
else
{
S_Base_StopBackgroundTrack();
return;
}
}
}
}
/*
======================
S_FreeOldestSound
======================
*/
void S_FreeOldestSound( void ) {
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;
}