mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-11-26 22:01:50 +00:00
96a9e2a9aa
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
1624 lines
36 KiB
C
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;
|
|
}
|