/*
===========================================================================
Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
RTCW MP 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 3 of the License, or
(at your option) any later version.
RTCW MP 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 RTCW MP Source Code. If not, see .
In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
/*****************************************************************************
* name: snd_dma.c
*
* desc: main control for any streaming sound output device
*
* $Archive: /Wolfenstein MP/src/client/snd_dma.c $
*
*****************************************************************************/
#include "snd_local.h"
#include "client.h"
void S_Play_f( void );
void S_SoundList_f( void );
void S_Music_f( void );
void S_StreamingSound_f( void );
void S_Update_Mix();
void S_StopAllSounds( void );
void S_UpdateStreamingSounds( void );
// Ridah, streaming sounds
// !! NOTE: the first streaming sound is always the music
streamingSound_t streamingSounds[MAX_STREAMING_SOUNDS];
int numStreamingSounds = 0;
static vec3_t entityPositions[MAX_GENTITIES];
void *crit;
typedef struct {
vec3_t origin;
qboolean fixedOrigin;
int entityNum;
int entityChannel;
sfxHandle_t sfx;
int flags;
} s_pushStack;
#define MAX_PUSHSTACK 64
static s_pushStack pushPop[MAX_PUSHSTACK];
static int tart = 0;
typedef struct {
char intro[256];
char loop[256];
qboolean music;
int entnum;
int channel;
int attenuation;
} s_streamStack;
//static s_streamStack Sstream[MAX_PUSHSTACK]; // TTimo: unused
//static int onStream; // 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
#define SOUND_RANGE_DEFAULT 1250
channel_t s_channels[MAX_CHANNELS];
channel_t loop_channels[MAX_CHANNELS];
int numLoopChannels;
static int s_soundStarted;
static qboolean s_soundMuted;
static qboolean s_soundPainted;
static int s_clearSoundBuffer = 0;
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_volume;
cvar_t *s_testsound;
cvar_t *s_khz;
cvar_t *s_show;
cvar_t *s_mixahead;
cvar_t *s_mixPreStep;
cvar_t *s_musicVolume;
cvar_t *s_separation;
cvar_t *s_doppler;
cvar_t *s_mute; // (SA) for DM so he can 'toggle' sound on/off without disturbing volume levels
cvar_t *s_defaultsound; // (SA) added to silence the default beep sound if desired
cvar_t *cl_cacheGathering; // Ridah
cvar_t *s_wavonly;
#define MAX_LOOP_SOUNDS 128
static int numLoopSounds;
static loopSound_t loopSounds[MAX_LOOP_SOUNDS];
static channel_t *freelist = NULL;
static channel_t *endflist = NULL;
// Rafael
cvar_t *s_nocompressed;
// for streaming sounds
int s_rawend[MAX_STREAMING_SOUNDS];
int s_rawpainted[MAX_STREAMING_SOUNDS];
portable_samplepair_t s_rawsamples[MAX_STREAMING_SOUNDS][MAX_RAW_SAMPLES];
// RF, store the volumes, since now they get adjusted at time of painting, so we can extract talking data first
portable_samplepair_t s_rawVolume[MAX_STREAMING_SOUNDS];
/*
================
S_SoundInfo_f
================
*/
void S_SoundInfo_f( void ) {
Com_Printf( "----- Sound Info -----\n" );
if ( !s_soundStarted ) {
Com_Printf( "sound system not started\n" );
} else {
if ( s_soundMuted ) {
Com_Printf( "sound system is muted\n" );
}
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( "0x%x dma buffer\n", dma.buffer );
if ( streamingSounds[0].file ) {
Com_Printf( "Background file: %s\n", streamingSounds[0].loop );
} else {
Com_Printf( "No background file.\n" );
}
}
Com_Printf( "----------------------\n" );
}
void S_ChannelSetup();
/*
================
S_Init
================
*/
void S_Init( void ) {
cvar_t *cv;
qboolean r;
Com_Printf( "\n------- sound initialization -------\n" );
s_mute = Cvar_Get( "s_mute", "0", CVAR_TEMP ); //----(SA) added
s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE );
s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE );
s_separation = Cvar_Get( "s_separation", "0.5", CVAR_ARCHIVE );
s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE );
s_khz = Cvar_Get( "s_khz", "22", CVAR_ARCHIVE );
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 );
s_defaultsound = Cvar_Get( "s_defaultsound", "0", CVAR_ARCHIVE );
s_wavonly = Cvar_Get( "s_wavonly", "0", CVAR_ARCHIVE | CVAR_LATCH );
// Ridah
cl_cacheGathering = Cvar_Get( "cl_cacheGathering", "0", 0 );
// Rafael
s_nocompressed = Cvar_Get( "s_nocompressed", "0", CVAR_INIT );
cv = Cvar_Get( "s_initsound", "1", 0 );
if ( !cv->integer ) {
Com_Printf( "not initializing.\n" );
Com_Printf( "------------------------------------\n" );
return;
}
crit = Sys_InitializeCriticalSection();
Cmd_AddCommand( "play", S_Play_f );
Cmd_AddCommand( "music", S_Music_f );
Cmd_AddCommand( "streamingsound", S_StreamingSound_f );
Cmd_AddCommand( "s_list", S_SoundList_f );
Cmd_AddCommand( "s_info", S_SoundInfo_f );
Cmd_AddCommand( "s_stop", S_StopAllSounds );
r = SNDDMA_Init();
Com_Printf( "------------------------------------\n" );
if ( r ) {
Com_Memset( sfxHash, 0, sizeof( sfx_t * ) * LOOP_HASH );
s_soundStarted = 1;
s_soundMuted = 1;
// s_numSfx = 0;
s_soundtime = 0;
s_paintedtime = 0;
S_StopAllSounds();
S_SoundInfo_f();
S_ChannelSetup();
}
}
/*
================
S_ChannelFree
================
*/
void S_ChannelFree( channel_t *v ) {
v->thesfx = NULL;
v->threadReady = qfalse;
*(channel_t **)endflist = v;
endflist = v;
*(channel_t **)v = NULL;
}
/*
================
S_ChannelMalloc
================
*/
channel_t* S_ChannelMalloc() {
channel_t *v;
if ( freelist == NULL ) {
return NULL;
}
v = freelist;
freelist = *(channel_t **)freelist;
v->allocTime = Sys_Milliseconds();
return v;
}
/*
================
S_ChannelSetup
================
*/
void S_ChannelSetup() {
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;
}
endflist = q;
*(channel_t **)q = NULL;
freelist = p + MAX_CHANNELS - 1;
Com_DPrintf( "Channel memory manager started\n" );
}
/*
================
S_Shutdown
================
*/
void S_Shutdown( void ) {
if ( !s_soundStarted ) {
return;
}
Sys_EnterCriticalSection( crit );
SNDDMA_Shutdown();
s_soundStarted = 0;
s_soundMuted = qtrue;
Cmd_RemoveCommand( "play" );
Cmd_RemoveCommand( "music" );
Cmd_RemoveCommand( "stopsound" );
Cmd_RemoveCommand( "soundlist" );
Cmd_RemoveCommand( "soundinfo" );
}
/*
================
S_HashSFXName
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_FreeOldestSound
======================
*/
void S_FreeOldestSound( void ) {
int i, oldest, used;
sfx_t *sfx;
sndBuffer *buffer, *nbuffer;
oldest = Sys_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];
// DHM - Nerve :: can cause race conditions
//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;
}
/*
==================
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, "S_FindName: NULL\n");
name = "*default*";
}
if ( !name[0] ) {
//Com_Error (ERR_FATAL, "S_FindName: empty name\n");
name = "*default*";
}
if ( strlen( name ) >= MAX_QPATH ) {
Com_Error( ERR_FATAL, "Sound name too long: %s", name );
}
// Ridah, caching
if ( cl_cacheGathering->integer ) {
Cbuf_ExecuteText( EXEC_NOW, va( "cache_usedfile sound %s\n", name ) );
}
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;
if ( s_defaultsound->integer ) {
for ( i = 0 ; i < sfx->soundLength ; i++ ) {
sfx->soundData->sndChunk[i] = i;
}
} else {
for ( i = 0 ; i < sfx->soundLength ; i++ ) {
sfx->soundData->sndChunk[i] = 0;
}
}
}
/*
===================
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_DisableSounds( void ) {
S_StopAllSounds();
s_soundMuted = qtrue;
}
/*
=====================
S_BeginRegistration
=====================
*/
void S_BeginRegistration( void ) {
sfx_t *sfx;
s_soundMuted = qfalse; // we can play again
if ( s_numSfx == 0 ) {
SND_setup();
s_numSfx = 0;
Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) );
Com_Memset( sfxHash, 0, sizeof( sfx_t * ) * LOOP_HASH );
sfx = S_FindName( "***DEFAULT***" );
S_DefaultSound( sfx );
}
}
/*
==================
S_RegisterSound
Creates a default buzz sound if the file can't be loaded
==================
*/
sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) {
sfx_t *sfx;
if ( !s_soundStarted ) {
return 0;
}
if ( strlen( name ) >= MAX_QPATH ) {
Com_DPrintf( "Sound name exceeds MAX_QPATH\n" );
return 0;
}
sfx = S_FindName( name );
if ( sfx->soundData ) {
if ( sfx->defaultSound ) {
if ( com_developer->integer ) {
Com_DPrintf( 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;
// if (!compressed) {
S_memoryLoad( sfx );
// }
if ( sfx->defaultSound ) {
if ( com_developer->integer ) {
Com_DPrintf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName );
}
return 0;
}
return sfx - s_knownSfx;
}
/*
=================
S_memoryLoad
=================
*/
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, float range ) {
vec_t dot;
vec_t dist;
vec_t lscale, rscale, scale;
vec3_t source_vec;
vec3_t vec;
// const float dist_mult = SOUND_ATTENUATE;
float dist_mult, dist_fullvol;
dist_fullvol = range * 0.064f; // default range of 1250 gives 80
dist_mult = dist_fullvol * 0.00001f; // default range of 1250 gives .0008
// dist_mult = range*0.00000064f; // default range of 1250 gives .0008
// calculate stereo seperation and distance attenuation
VectorSubtract( origin, listener_origin, source_vec );
dist = VectorNormalize( source_vec );
// dist -= SOUND_FULLVOLUME;
dist -= dist_fullvol;
if ( dist < 0 ) {
dist = 0; // close enough to be at full volume
}
if ( dist ) {
dist = dist / range; // FIXME: lose the divide again
}
// 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 );
//rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot;
//lscale = s_separation->value - ( 1.0 - s_separation->value ) * 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;
}
}
/*
====================
S_StartSound
Validates the parms and queues the sound up
if pos is NULL, the sound will be dynamically sourced from the entity
Entchannel 0 will never override a playing sound
flags: (currently apply only to non-looping sounds)
SND_NORMAL 0 - (default) allow sound to be cut off only by the same sound on this channel
SND_OKTOCUT 0x001 - allow sound to be cut off by any following sounds on this channel
SND_REQUESTCUT 0x002 - allow sound to be cut off by following sounds on this channel only for sounds who request cutoff
SND_CUTOFF 0x004 - cut off sounds on this channel that are marked 'SND_REQUESTCUT'
SND_CUTOFF_ALL 0x008 - cut off all sounds on this channel
====================
*/
void S_StartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, int flags ) {
if ( !s_soundStarted || s_soundMuted || ( cls.state != CA_ACTIVE && cls.state != CA_DISCONNECTED ) ) {
return;
}
if ( tart < MAX_PUSHSTACK ) {
sfx_t *sfx;
if ( origin ) {
VectorCopy( origin, pushPop[tart].origin );
pushPop[tart].fixedOrigin = qtrue;
} else {
pushPop[tart].fixedOrigin = qfalse;
}
pushPop[tart].entityNum = entityNum;
pushPop[tart].entityChannel = entchannel;
pushPop[tart].sfx = sfxHandle;
pushPop[tart].flags = flags;
sfx = &s_knownSfx[ sfxHandle ];
if ( sfx->inMemory == qfalse ) {
S_memoryLoad( sfx );
}
tart++;
}
}
void S_ThreadStartSoundEx( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, int flags ) {
channel_t *ch;
sfx_t *sfx;
int i, oldest, chosen;
chosen = -1;
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_DPrintf( S_COLOR_YELLOW, "S_StartSound: handle %i out of range\n", sfxHandle );
return;
}
sfx = &s_knownSfx[ sfxHandle ];
if ( s_show->integer == 1 ) {
Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName );
}
// Com_Printf("playing %s\n", sfx->soundName);
sfx->lastTimeUsed = Sys_Milliseconds();
// check for a streaming sound that this entity is playing in this channel
// kill it if it exists
if ( entityNum >= 0 ) {
for ( i = 1; i < MAX_STREAMING_SOUNDS; i++ ) { // track 0 is music/cinematics
if ( !streamingSounds[i].file ) {
continue;
}
// check to see if this character currently has another sound streaming on the same channel
if ( ( entchannel != CHAN_AUTO ) && ( streamingSounds[i].entnum >= 0 ) && ( streamingSounds[i].channel == entchannel ) && ( streamingSounds[i].entnum == entityNum ) ) {
// found a match, override this channel
streamingSounds[i].kill = qtrue;
break;
}
}
}
ch = NULL;
//----(SA) modified
// shut off other sounds on this channel if necessary
for ( i = 0 ; i < MAX_CHANNELS ; i++ ) {
if ( s_channels[i].entnum == entityNum && s_channels[i].thesfx && s_channels[i].entchannel == entchannel ) {
// cutoff all on channel
if ( flags & SND_CUTOFF_ALL ) {
S_ChannelFree( &s_channels[i] );
continue;
}
if ( s_channels[i].flags & SND_NOCUT ) {
continue;
}
// cutoff sounds that expect to be overwritten
if ( s_channels[i].flags & SND_OKTOCUT ) {
S_ChannelFree( &s_channels[i] );
continue;
}
// cutoff 'weak' sounds on channel
if ( flags & SND_CUTOFF ) {
if ( s_channels[i].flags & SND_REQUESTCUT ) {
S_ChannelFree( &s_channels[i] );
continue;
}
}
}
}
// re-use channel if applicable
for ( i = 0 ; i < MAX_CHANNELS ; i++ ) {
if ( s_channels[i].entnum == entityNum && s_channels[i].entchannel == entchannel ) {
if ( !( s_channels[i].flags & SND_NOCUT ) && s_channels[i].thesfx == sfx ) {
ch = &s_channels[i];
break;
}
}
}
if ( !ch ) {
ch = S_ChannelMalloc();
}
//----(SA) end
if ( !ch ) {
ch = s_channels;
oldest = sfx->lastTimeUsed;
for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
if ( ch->entnum == entityNum && ch->thesfx == sfx ) {
chosen = i;
break;
}
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 ) {
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->flags = flags; //----(SA) added
ch->master_vol = 127;
ch->entnum = entityNum;
ch->thesfx = sfx;
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;
if ( ch->fixed_origin ) {
S_SpatializeOrigin( ch->origin, ch->master_vol, &ch->leftvol, &ch->rightvol, SOUND_RANGE_DEFAULT );
} else {
S_SpatializeOrigin( entityPositions[ ch->entnum ], ch->master_vol, &ch->leftvol, &ch->rightvol, SOUND_RANGE_DEFAULT );
}
ch->startSample = START_SAMPLE_IMMEDIATE;
ch->threadReady = qtrue;
}
/*
==============
S_StartSound
==============
*/
void S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) {
S_StartSoundEx( origin, entityNum, entchannel, sfxHandle, 0 );
}
/*
==================
S_StartLocalSound
==================
*/
void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) {
if ( !s_soundStarted || s_soundMuted ) {
return;
}
if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
Com_DPrintf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle );
return;
}
S_StartSound( NULL, listener_number, channelNum, sfxHandle );
}
/*
==================
S_ClearSoundBuffer
If we are about to perform file access, clear the buffer
so sound doesn't stutter.
==================
*/
void S_ClearSoundBuffer( void ) {
if ( !s_soundStarted ) {
return;
}
if ( !s_soundPainted ) { // RF, buffers are clear, no point clearing again
return;
}
s_soundPainted = qfalse;
s_clearSoundBuffer = 4;
S_Update(); // NERVE - SMF - force an update
}
/*
==================
S_StopAllSounds
==================
*/
void S_StopAllSounds( void ) {
int i;
if ( !s_soundStarted ) {
return;
}
//DAJ BUGFIX for(i=0;i= MAX_LOOP_SOUNDS ) {
return;
}
if ( !volume ) {
return;
}
if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) {
Com_Error( ERR_DROP, "S_AddLoopingSound: handle %i out of range", sfxHandle );
}
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[numLoopSounds].origin );
VectorCopy( velocity, loopSounds[numLoopSounds].velocity );
loopSounds[numLoopSounds].sfx = sfx;
if ( range ) {
loopSounds[numLoopSounds].range = range;
} else {
loopSounds[numLoopSounds].range = SOUND_RANGE_DEFAULT;
}
if ( volume & 1 << UNDERWATER_BIT ) {
loopSounds[numLoopSounds].loudUnderWater = qtrue;
}
if ( volume > 255 ) {
volume = 255;
} else if ( volume < 0 ) {
volume = 0;
}
loopSounds[numLoopSounds].vol = volume;
numLoopSounds++;
}
/*
==================
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;
// Sys_EnterCriticalSection(crit);
numLoopChannels = 0;
time = Sys_Milliseconds();
loopFrame++;
for ( i = 0 ; i < numLoopSounds ; i++ ) {
loop = &loopSounds[i];
if ( loop->mergeFrame == loopFrame ) {
continue; // already merged into an earlier sound
}
//if (loop->kill) {
// S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total, loop->range); // 3d
//} else {
S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total, loop->range ); // sphere
//}
// adjust according to volume
left_total = (int)( (float)loop->vol * (float)left_total / 256.0 );
right_total = (int)( (float)loop->vol * (float)right_total / 256.0 );
loop->sfx->lastTimeUsed = time;
for ( j = ( i + 1 ); j < numLoopChannels ; j++ ) {
loop2 = &loopSounds[j];
if ( loop2->sfx != loop->sfx ) {
continue;
}
loop2->mergeFrame = loopFrame;
//if (loop2->kill) {
// S_SpatializeOrigin( loop2->origin, 127, &left, &right, loop2->range); // 3d
//} else {
S_SpatializeOrigin( loop2->origin, 90, &left, &right, loop2->range ); // sphere
//}
// adjust according to volume
left = (int)( (float)loop2->vol * (float)left / 256.0 );
right = (int)( (float)loop2->vol * (float)right / 256.0 );
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;
// RF, disabled doppler for looping sounds for now, since we are reverting to the old looping sound code
ch->doppler = qfalse;
numLoopChannels++;
if ( numLoopChannels == MAX_CHANNELS ) {
i = numLoopSounds + 1;
}
}
// Sys_LeaveCriticalSection(crit);
}
//=============================================================================
/*
=================
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.
=================
*/
//DAJ void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) {
void S_ByteSwapRawSamples( int samples, int width, int s_channels, short *data ) {
int i;
if ( width != 2 ) {
return;
}
#ifndef __MACOS__ //DAJ save this test
if ( LittleShort( 256 ) == 256 ) {
return;
}
#endif
//DAJ use a faster loop technique
if ( s_channels == 2 ) {
i = samples << 1;
} else {
i = samples;
}
do {
*data = LittleShort( *data );
data++;
//DAJ ((short *)data)[i] = LittleShort( ((short *)data)[i] );
} while ( --i );
}
/*
============
S_GetRawSamplePointer
============
*/
portable_samplepair_t *S_GetRawSamplePointer() {
return s_rawsamples[0];
}
/*
============
S_RawSamples
Music streaming
============
*/
void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float lvol, float rvol, int streamingIndex ) {
int i;
int src, dst;
float scale;
int intVolumeL, intVolumeR;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
// volume taken into account when mixed
s_rawVolume[streamingIndex].left = 256 * lvol;
s_rawVolume[streamingIndex].right = 256 * rvol;
intVolumeL = 256;
intVolumeR = 256;
if ( s_rawend[streamingIndex] < s_soundtime ) {
Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend[streamingIndex], s_soundtime );
s_rawend[streamingIndex] = s_soundtime;
}
scale = (float)rate / dma.speed;
//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend);
if ( s_channels == 2 && width == 2 ) {
if ( scale == 1.0 ) { // optimized case
for ( i = 0; i < samples; i++ )
{
dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 );
s_rawend[streamingIndex]++;
s_rawsamples[streamingIndex][dst].left = ( (short *)data )[i * 2] * intVolumeL;
s_rawsamples[streamingIndex][dst].right = ( (short *)data )[i * 2 + 1] * intVolumeR;
}
} else
{
for ( i = 0; ; i++ )
{
src = i * scale;
if ( src >= samples ) {
break;
}
dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 );
s_rawend[streamingIndex]++;
s_rawsamples[streamingIndex][dst].left = ( (short *)data )[src * 2] * intVolumeL;
s_rawsamples[streamingIndex][dst].right = ( (short *)data )[src * 2 + 1] * intVolumeR;
}
}
} else if ( s_channels == 1 && width == 2 ) {
for ( i = 0; ; i++ )
{
src = i * scale;
if ( src >= samples ) {
break;
}
dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 );
s_rawend[streamingIndex]++;
s_rawsamples[streamingIndex][dst].left = ( (short *)data )[src] * intVolumeL;
s_rawsamples[streamingIndex][dst].right = ( (short *)data )[src] * intVolumeR;
}
} else if ( s_channels == 2 && width == 1 ) {
intVolumeL *= 256;
intVolumeR *= 256;
for ( i = 0 ; ; i++ )
{
src = i * scale;
if ( src >= samples ) {
break;
}
dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 );
s_rawend[streamingIndex]++;
s_rawsamples[streamingIndex][dst].left = ( (char *)data )[src * 2] * intVolumeL;
s_rawsamples[streamingIndex][dst].right = ( (char *)data )[src * 2 + 1] * intVolumeR;
}
} else if ( s_channels == 1 && width == 1 ) {
intVolumeL *= 256;
intVolumeR *= 256;
for ( i = 0; ; i++ )
{
src = i * scale;
if ( src >= samples ) {
break;
}
dst = s_rawend[streamingIndex] & ( MAX_RAW_SAMPLES - 1 );
s_rawend[streamingIndex]++;
s_rawsamples[streamingIndex][dst].left = ( ( (byte *)data )[src] - 128 ) * intVolumeL;
s_rawsamples[streamingIndex][dst].right = ( ( (byte *)data )[src] - 128 ) * intVolumeR;
}
}
if ( s_rawend[streamingIndex] > s_soundtime + MAX_RAW_SAMPLES ) {
Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend[streamingIndex], s_soundtime );
}
}
//=============================================================================
/*
=====================
S_UpdateEntityPosition
let the sound system know where an entity currently is
======================
*/
void S_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, entityPositions[entityNum] );
}
/*
============
S_Respatialize
Change the volumes of all the playing sounds for changes in their positions
============
*/
void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) {
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] );
}
void S_ThreadRespatialize() {
int i;
channel_t *ch;
vec3_t origin;
// update spatialization for dynamic sounds
ch = s_channels;
for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) {
if ( !ch->thesfx ) {
continue;
}
// anything coming from the view entity will always be full volume
if ( ch->entnum == listener_number ) {
ch->leftvol = ch->master_vol;
ch->rightvol = ch->master_vol;
} else {
if ( ch->fixed_origin ) {
VectorCopy( ch->origin, origin );
} else {
VectorCopy( entityPositions[ ch->entnum ], origin );
}
S_SpatializeOrigin( origin, ch->master_vol, &ch->leftvol, &ch->rightvol, SOUND_RANGE_DEFAULT );
}
}
}
/*
========================
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->threadReady == qtrue ) {
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_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_DPrintf( "%i %i %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName ); // <- this is not thread safe
total++;
}
}
Com_Printf( "----(%i)---- painted: %i\n", total, s_paintedtime );
}
// add loopsounds
S_AddLoopSounds();
// do all the rest
S_UpdateThread();
}
void S_UpdateThread( void ) {
if ( !s_soundStarted || s_soundMuted ) {
// Com_DPrintf ("not started or muted\n");
return;
}
#ifdef TALKANIM
// default to ZERO amplitude, overwrite if sound is playing
memset( s_entityTalkAmplitude, 0, sizeof( s_entityTalkAmplitude ) );
#endif
if ( s_clearSoundBuffer ) {
int clear;
int i;
Sys_EnterCriticalSection( crit );
// stop looping sounds
S_ClearLoopingSounds();
for ( i = 0; i < MAX_STREAMING_SOUNDS; i++ ) {
s_rawend[i] = 0;
}
if ( dma.samplebits == 8 ) {
clear = 0x80;
} else {
clear = 0;
}
SNDDMA_BeginPainting();
if ( dma.buffer ) {
// TTimo: due to a particular bug workaround in linux sound code,
// have to optionally use a custom C implementation of Com_Memset
// not affecting win32, we have #define Snd_Memset Com_Memset
// show_bug.cgi?id=371
Snd_Memset( dma.buffer, clear, dma.samples * dma.samplebits / 8 );
}
SNDDMA_Submit();
s_clearSoundBuffer = 0;
Sys_LeaveCriticalSection( crit );
// NERVE - SMF - clear out channels so they don't finish playing when audio restarts
S_ChannelSetup();
} else {
Sys_EnterCriticalSection( crit );
S_ThreadRespatialize();
// add raw data from streamed samples
S_UpdateStreamingSounds();
// mix some sound
S_Update_Mix();
Sys_LeaveCriticalSection( crit );
}
}
/*
============
S_GetSoundtime
============
*/
void S_GetSoundtime( void ) {
int samplepos;
static int buffers;
static int oldsamplepos;
int fullsamples;
fullsamples = dma.samples / dma.channels;
// it is possible to miscount buffers if it has wrapped twice between
// calls to S_Update. Oh well.
samplepos = SNDDMA_GetDMAPos();
if ( samplepos < oldsamplepos ) {
buffers++; // buffer wrapped
if ( s_paintedtime > 0x40000000 ) { // time to chop things off to avoid 32 bit limits
buffers = 0;
s_paintedtime = fullsamples;
S_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_GetSoundtime : 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;
}
}
/*
============
S_Update_Mix
============
*/
void S_Update_Mix( void ) {
unsigned endtime;
int samps, i;
static float lastTime = 0.0f;
float ma, op;
float thisTime, sane;
if ( !s_soundStarted || s_soundMuted ) {
return;
}
for ( i = 0; i < tart; i++ ) {
if ( pushPop[i].fixedOrigin ) {
S_ThreadStartSoundEx( pushPop[i].origin, pushPop[i].entityNum, pushPop[i].entityChannel, pushPop[i].sfx, pushPop[i].flags );
} else {
S_ThreadStartSoundEx( NULL, pushPop[i].entityNum, pushPop[i].entityChannel, pushPop[i].sfx, pushPop[i].flags );
}
}
tart = 0;
s_soundPainted = qtrue;
thisTime = Sys_Milliseconds();
// Updates s_soundtime
S_GetSoundtime();
// 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;
}
/*
===============================================================================
console functions
===============================================================================
*/
void S_Play_f( void ) {
int i;
sfxHandle_t h;
char name[256];
i = 1;
while ( i < Cmd_Argc() ) {
if ( !Q_strrchr( Cmd_Argv( i ), '.' ) ) {
Com_sprintf( name, sizeof( name ), "%s.wav", Cmd_Argv( 1 ) );
} else {
Q_strncpyz( name, Cmd_Argv( i ), sizeof( name ) );
}
h = S_RegisterSound( name, qfalse );
if ( h ) {
S_StartLocalSound( h, CHAN_LOCAL_SOUND );
}
i++;
}
}
void S_Music_f( void ) {
int c;
c = Cmd_Argc();
if ( c == 2 ) {
S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 1 ) );
} else if ( c == 3 ) {
S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ) );
Q_strncpyz( streamingSounds[0].loop, Cmd_Argv( 2 ), sizeof( streamingSounds[0].loop ) );
} else {
Com_Printf( "music [loopfile]\n" );
return;
}
}
// Ridah, just for testing the streaming sounds
void S_StreamingSound_f( void ) {
int c;
c = Cmd_Argc();
if ( c == 2 ) {
S_StartStreamingSound( Cmd_Argv( 1 ), 0, -1, 0, 0 );
} else if ( c == 5 ) {
S_StartStreamingSound( Cmd_Argv( 1 ), 0, atoi( Cmd_Argv( 2 ) ), atoi( Cmd_Argv( 3 ) ), atoi( Cmd_Argv( 4 ) ) );
} else {
Com_Printf( "streamingsound [entnum channel attenuation]\n" );
return;
}
}
void S_SoundList_f( 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();
}
/*
===============================================================================
STREAMING SOUND
===============================================================================
*/
int FGetLittleLong( fileHandle_t f ) {
int v;
FS_Read( &v, sizeof( v ), f );
return LittleLong( v );
}
int FGetLittleShort( fileHandle_t f ) {
short v;
FS_Read( &v, sizeof( v ), f );
return LittleShort( v );
}
// returns the length of the data in the chunk, or 0 if not found
int S_FindWavChunk( fileHandle_t f, char *chunk ) {
char name[5];
int len;
int r;
name[4] = 0;
len = 0;
r = FS_Read( name, 4, f );
if ( r != 4 ) {
return 0;
}
len = FGetLittleLong( f );
if ( len < 0 || len > 0xfffffff ) {
len = 0;
return 0;
}
len = ( len + 1 ) & ~1; // pad to word boundary
// s_nextWavChunk += len + 8;
if ( strcmp( name, chunk ) ) {
return 0;
}
return len;
}
/*
======================
S_StartBackgroundTrack
======================
*/
void S_StartBackgroundTrack( const char *intro, const char *loop ) {
int len;
char dump[16];
char name[MAX_QPATH];
int i;
streamingSound_t *ss;
fileHandle_t fh;
// if (!s_soundStarted || !crit) {
// return;
// }
Sys_EnterCriticalSection( crit );
if ( !intro ) {
intro = "";
}
if ( !loop || !loop[0] ) {
loop = intro;
}
Com_DPrintf( "S_StartBackgroundTrack( %s, %s )...\n", intro, loop );
// music is always track 0
i = 0;
ss = &streamingSounds[i];
Q_strncpyz( ss->loop, loop, sizeof( ss->loop ) - 4 );
Q_strncpyz( name, intro, sizeof( name ) - 4 );
COM_DefaultExtension( name, sizeof( name ), ".wav" );
// close the current sound if present, but DON'T reset s_rawend
if ( ss->file ) {
Sys_EndStreamedFile( ss->file );
FS_FCloseFile( ss->file );
ss->file = 0;
}
if ( !intro[0] ) {
Com_DPrintf( "Fail to start: %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
ss->channel = 0;
ss->entnum = -1;
ss->attenuation = 0;
//
// open up a wav file and get all the info
//
FS_FOpenFileRead( name, &fh, qtrue );
if ( !fh ) {
Com_Printf( "Couldn't open streaming sound file %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
// skip the riff wav header
FS_Read( dump, 12, fh );
if ( !S_FindWavChunk( fh, "fmt " ) ) {
Com_Printf( "No fmt chunk in %s\n", name );
FS_FCloseFile( fh );
Sys_LeaveCriticalSection( crit );
return;
}
// save name for soundinfo
ss->info.format = FGetLittleShort( fh );
ss->info.channels = FGetLittleShort( fh );
ss->info.rate = FGetLittleLong( fh );
FGetLittleLong( fh );
FGetLittleShort( fh );
ss->info.width = FGetLittleShort( fh ) / 8;
if ( ss->info.format != WAV_FORMAT_PCM ) {
FS_FCloseFile( fh );
Com_Printf( "Not a microsoft PCM format wav: %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
if ( ss->info.channels != 2 || ss->info.rate != 22050 ) {
Com_Printf( "WARNING: music file %s is not 22k stereo\n", name );
}
if ( ( len = S_FindWavChunk( fh, "data" ) ) == 0 ) {
FS_FCloseFile( fh );
Com_Printf( "No data chunk in %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
ss->info.samples = len / ( ss->info.width * ss->info.channels );
ss->samples = ss->info.samples;
//
// start the background streaming
//
Sys_BeginStreamedFile( fh, 0x10000 );
ss->file = fh;
ss->kill = qfalse;
numStreamingSounds++;
Com_DPrintf( "S_StartBackgroundTrack - Success\n", intro, loop );
Sys_LeaveCriticalSection( crit );
}
/*
======================
S_StartStreamingSound
FIXME: record the starting cg.time of the sound, so we can determine the
position by looking at the current cg.time, this way pausing or loading a
savegame won't screw up the timing of important sounds
======================
*/
void S_StartStreamingSound( const char *intro, const char *loop, int entnum, int channel, int attenuation ) {
int len;
char dump[16];
char name[MAX_QPATH];
int i;
streamingSound_t *ss;
fileHandle_t fh;
if ( !crit || !s_soundStarted || s_soundMuted || cls.state != CA_ACTIVE ) {
return;
}
Sys_EnterCriticalSection( crit );
if ( !intro || !intro[0] ) {
if ( loop && loop[0] ) {
intro = loop;
} else {
intro = "";
}
}
Com_DPrintf( "S_StartStreamingSound( %s, %s, %i, %i, %i )\n", intro, loop, entnum, channel, attenuation );
// look for a free track, but first check for overriding a currently playing sound for this entity
ss = NULL;
if ( entnum >= 0 ) {
for ( i = 1; i < MAX_STREAMING_SOUNDS; i++ ) { // track 0 is music/cinematics
if ( !streamingSounds[i].file ) {
continue;
}
// check to see if this character currently has another sound streaming on the same channel
if ( ( channel != CHAN_AUTO ) && ( streamingSounds[i].entnum >= 0 ) && ( streamingSounds[i].channel == channel ) && ( streamingSounds[i].entnum == entnum ) ) {
// found a match, override this channel
streamingSounds[i].kill = qtrue;
ss = &streamingSounds[i]; // use this track to start the new stream
break;
}
}
}
if ( !ss ) {
// no need to override a current stream, so look for a free track
for ( i = 1; i < MAX_STREAMING_SOUNDS; i++ ) { // track 0 is music/cinematics
if ( !streamingSounds[i].file ) {
ss = &streamingSounds[i];
break;
}
}
}
if ( !ss ) {
if ( !s_mute->integer ) { // don't do the print if you're muted
Com_Printf( "S_StartStreamingSound: No free streaming tracks\n" );
}
Sys_LeaveCriticalSection( crit );
return;
}
if ( ss->loop && loop ) {
Q_strncpyz( ss->loop, loop, sizeof( ss->loop ) - 4 );
} else {
ss->loop[0] = 0;
}
Q_strncpyz( name, intro, sizeof( name ) - 4 );
COM_DefaultExtension( name, sizeof( name ), ".wav" );
// close the current sound if present, but DON'T reset s_rawend
if ( ss->file ) {
Sys_EndStreamedFile( ss->file );
FS_FCloseFile( ss->file );
ss->file = 0;
}
if ( !intro[0] ) {
Sys_LeaveCriticalSection( crit );
return;
}
//
// open up a wav file and get all the info
//
FS_FOpenFileRead( name, &fh, qtrue );
if ( !fh ) {
Com_Printf( "Couldn't open streaming sound file %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
// skip the riff wav header
FS_Read( dump, 12, fh );
if ( !S_FindWavChunk( fh, "fmt " ) ) {
Com_Printf( "No fmt chunk in %s\n", name );
FS_FCloseFile( fh );
Sys_LeaveCriticalSection( crit );
return;
}
// save name for soundinfo
ss->info.format = FGetLittleShort( fh );
ss->info.channels = FGetLittleShort( fh );
ss->info.rate = FGetLittleLong( fh );
FGetLittleLong( fh );
FGetLittleShort( fh );
ss->info.width = FGetLittleShort( fh ) / 8;
if ( ss->info.format != WAV_FORMAT_PCM ) {
FS_FCloseFile( fh );
Com_Printf( "Not a microsoft PCM format wav: %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
//if ( ss->info.channels != 2 || ss->info.rate != 22050 ) {
// Com_Printf("WARNING: music file %s is not 22k stereo\n", name );
//}
if ( ( len = S_FindWavChunk( fh, "data" ) ) == 0 ) {
FS_FCloseFile( fh );
Com_Printf( "No data chunk in %s\n", name );
Sys_LeaveCriticalSection( crit );
return;
}
ss->info.samples = len / ( ss->info.width * ss->info.channels );
ss->samples = ss->info.samples;
ss->channel = channel;
ss->attenuation = attenuation;
ss->entnum = entnum;
ss->kill = qfalse;
//
// start the background streaming
//
Sys_BeginStreamedFile( fh, 0x10000 );
ss->file = fh;
numStreamingSounds++;
Sys_LeaveCriticalSection( crit );
}
/*
======================
S_StopStreamingSound
======================
*/
void S_StopStreamingSound( int index ) {
if ( !streamingSounds[index].file ) {
return;
}
Sys_EnterCriticalSection( crit );
streamingSounds[index].kill = qtrue;
Sys_LeaveCriticalSection( crit );
}
/*
======================
S_StopBackgroundTrack
======================
*/
void S_StopBackgroundTrack( void ) {
S_StopStreamingSound( 0 );
}
/*
======================
S_UpdateStreamingSounds
======================
*/
void S_UpdateStreamingSounds( void ) {
int bufferSamples;
int fileSamples;
byte raw[30000]; // just enough to fit in a mac stack frame
int fileBytes;
int r, i;
streamingSound_t *ss;
int *re, *rp;
qboolean looped;
float lvol, rvol;
int soundMixAheadTime;
// if (!s_soundStarted || !crit) {
// return;
// }
if ( s_mute->value ) { //----(SA) sound is muted, skip everything
return;
}
soundMixAheadTime = s_soundtime; // + (int)(0.35 * dma.speed); // allow for talking animations
//----(SA) it seems this could potentially be in the wrong place.
// The intended purpose is to just quiet all sounds if s_mute is set (like a TV mute button)
// however, it seems the location here could potentially cause some streaming sound updates
// to not happen properly, so if you mute and un-mute while listening to a conversation
// you could screw up the timing. Is that the case?
// Ryan, could you give it a quick once-over to see if this is okay?
//
// (go ahead and delete commentary when you read)
s_soundPainted = qtrue;
for ( i = 0, ss = streamingSounds, re = s_rawend, rp = s_rawpainted; i < MAX_STREAMING_SOUNDS; i++, ss++, re++, rp++ ) {
if ( ss->kill ) {
fileHandle_t file;
file = ss->file;
ss->file = 0;
Sys_EndStreamedFile( file );
FS_FCloseFile( file );
numStreamingSounds--;
ss->kill = qfalse;
continue;
}
*rp = qfalse;
if ( !ss->file ) {
continue;
}
// don't bother playing anything if musicvolume is 0
if ( i == 0 && s_musicVolume->value <= 0 ) {
continue;
}
if ( i > 0 && s_volume->value <= 0 ) {
continue;
}
// see how many samples should be copied into the raw buffer
if ( *re < soundMixAheadTime ) { // RF, read a bit ahead of time to allow for talking animations
*re = soundMixAheadTime;
}
looped = qfalse;
while ( *re < soundMixAheadTime + MAX_RAW_SAMPLES ) {
bufferSamples = MAX_RAW_SAMPLES - ( *re - soundMixAheadTime );
// decide how much data needs to be read from the file
fileSamples = bufferSamples * ss->info.rate / dma.speed;
// if there are no samples due to be read this frame, abort painting
// but keep the streaming going, since it might just need to wait until
// the next frame before it needs to paint some more
if ( !fileSamples ) {
break;
}
// don't try and read past the end of the file
if ( fileSamples > ss->samples ) {
fileSamples = ss->samples;
}
// our max buffer size
fileBytes = fileSamples * ( ss->info.width * ss->info.channels );
if ( fileBytes > sizeof( raw ) ) {
fileBytes = sizeof( raw );
fileSamples = fileBytes / ( ss->info.width * ss->info.channels );
}
r = Sys_StreamedRead( raw, 1, fileBytes, ss->file );
if ( r != fileBytes ) {
Com_DPrintf( "StreamedRead failure on stream sound\n" );
ss->kill = qtrue;
break;
}
// byte swap if needed
S_ByteSwapRawSamples( fileSamples, ss->info.width, ss->info.channels, (short*)raw );
// calculate the volume
if ( i == 0 ) { // music
lvol = rvol = s_musicVolume->value;
} else { // attenuate if required
if ( ss->entnum >= 0 && ss->attenuation ) {
int r, l;
S_SpatializeOrigin( entityPositions[ ss->entnum ], s_volume->value * 255.0f, &l, &r, SOUND_RANGE_DEFAULT );
if ( ( lvol = ( (float)l / 255.0 ) ) > 1.0 ) {
lvol = 1.0;
}
if ( ( rvol = ( (float)r / 255.0 ) ) > 1.0 ) {
rvol = 1.0;
}
} else {
lvol = rvol = s_volume->value;
}
}
// add to raw buffer
S_RawSamples( fileSamples, ss->info.rate,
ss->info.width, ss->info.channels, raw, lvol, rvol, i );
*rp = qtrue;
ss->samples -= fileSamples;
if ( !ss->samples ) {
if ( ss->loop && ss->loop[0] ) {
// loop
if ( looped ) {
// already looped once
//*re = 0;
break;
} else {
char dump[16];
Sys_StreamSeek( ss->file, 0, FS_SEEK_SET );
FS_Read( dump, 12, ss->file );
if ( !S_FindWavChunk( ss->file, "fmt " ) ) {
ss->kill = qtrue;
break;
}
// save name for soundinfo
ss->info.format = FGetLittleShort( ss->file );
ss->info.channels = FGetLittleShort( ss->file );
ss->info.rate = FGetLittleLong( ss->file );
FGetLittleLong( ss->file );
FGetLittleShort( ss->file );
ss->info.width = FGetLittleShort( ss->file ) / 8;
looped = qtrue;
ss->samples = ss->info.samples;
if ( ( S_FindWavChunk( ss->file, "data" ) ) == 0 ) {
ss->kill = qtrue;
return;
}
}
} else {
// no loop, just stop
ss->kill = qtrue;;
break;
}
}
}
}
}