/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * * ======================================================================= * * Upper layer of the sound output. This is implemented via DMA, thus * needs a DMA capable lower level implementation for painting the * sounds to the device. Otherwise it'll be horible slow and stuttering. * * ======================================================================= */ #include "../header/client.h" #include "header/local.h" #include "header/vorbis.h" void S_Play ( void ); void S_SoundList ( void ); void S_Update_ (); void S_StopAllSounds ( void ); /* only begin attenuating sound volumes when outside the FULLVOLUME range */ #define SOUND_FULLVOLUME 80 #define SOUND_LOOPATTENUATE 0.003 int s_registration_sequence; channel_t channels [ MAX_CHANNELS ]; qboolean snd_initialized = false; int sound_started = 0; dma_t dma; vec3_t listener_origin; vec3_t listener_forward; vec3_t listener_right; vec3_t listener_up; static qboolean s_registering; static int soundtime; int paintedtime; /* during registration it is possible to have more sounds * than could actually be referenced during gameplay, * because we don't want to free anything until we are * sure we won't need it. */ #define MAX_SFX ( MAX_SOUNDS * 2 ) sfx_t known_sfx [ MAX_SFX ]; int num_sfx; #define MAX_PLAYSOUNDS 128 playsound_t s_playsounds [ MAX_PLAYSOUNDS ]; playsound_t s_freeplays; playsound_t s_pendingplays; int s_beginofs; cvar_t *s_volume; cvar_t *s_testsound; cvar_t *s_loadas8bit; cvar_t *s_khz; cvar_t *s_mixahead; cvar_t *s_show; int s_rawend; portable_samplepair_t s_rawsamples [ MAX_RAW_SAMPLES ]; /* * User-setable variables */ void S_SoundInfo_f ( void ) { if ( !sound_started ) { Com_Printf( "sound system not started\n" ); return; } Com_Printf( "%5d stereo\n", dma.channels - 1 ); Com_Printf( "%5d samples\n", dma.samples ); Com_Printf( "%5d samplepos\n", dma.samplepos ); Com_Printf( "%5d samplebits\n", dma.samplebits ); Com_Printf( "%5d submission_chunk\n", dma.submission_chunk ); Com_Printf( "%5d speed\n", dma.speed ); Com_Printf( "%p dma buffer\n", dma.buffer ); } void S_Init ( void ) { cvar_t *cv; Com_Printf( "\n------- sound initialization -------\n" ); cv = Cvar_Get( "s_initsound", "1", 0 ); if ( !cv->value ) { Com_Printf( "not initializing.\n" ); } else { s_volume = Cvar_Get( "s_volume", "0.7", CVAR_ARCHIVE ); s_khz = Cvar_Get( "s_khz", "22", CVAR_ARCHIVE ); s_loadas8bit = Cvar_Get( "s_loadas8bit", "0", CVAR_ARCHIVE ); s_mixahead = Cvar_Get( "s_mixahead", "0.14", CVAR_ARCHIVE ); s_show = Cvar_Get( "s_show", "0", 0 ); s_testsound = Cvar_Get( "s_testsound", "0", 0 ); Cmd_AddCommand( "play", S_Play ); Cmd_AddCommand( "stopsound", S_StopAllSounds ); Cmd_AddCommand( "soundlist", S_SoundList ); Cmd_AddCommand( "soundinfo", S_SoundInfo_f ); Cmd_AddCommand( "ogg_init", OGG_Init ); Cmd_AddCommand( "ogg_shutdown", OGG_Shutdown ); if ( !SNDDMA_Init() ) { return; } S_InitScaletable(); sound_started = 1; num_sfx = 0; soundtime = 0; paintedtime = 0; Com_Printf( "sound sampling rate: %i\n", dma.speed ); S_StopAllSounds(); OGG_Init(); } Com_Printf( "------------------------------------\n" ); } /* * Shutdown sound engine */ void S_Shutdown ( void ) { int i; sfx_t *sfx; if ( !sound_started ) { return; } /* free all sounds */ for ( i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++ ) { if ( !sfx->name [ 0 ] ) { continue; } if ( sfx->cache ) { Z_Free( sfx->cache ); } } memset( known_sfx, 0, sizeof ( known_sfx ) ); num_sfx = 0; sound_started = 0; OGG_Shutdown(); SNDDMA_Shutdown(); Cmd_RemoveCommand( "soundlist" ); Cmd_RemoveCommand( "soundinfo" ); Cmd_RemoveCommand( "play" ); Cmd_RemoveCommand( "stopsound" ); } /* * Returns the name of a sound */ sfx_t * S_FindName ( char *name, qboolean create ) { int i; sfx_t *sfx; if ( !name ) { Com_Error( ERR_FATAL, "S_FindName: NULL\n" ); } if ( !name [ 0 ] ) { Com_Error( ERR_FATAL, "S_FindName: empty name\n" ); } if ( strlen( name ) >= MAX_QPATH ) { Com_Error( ERR_FATAL, "Sound name too long: %s", name ); } /* see if already loaded */ for ( i = 0; i < num_sfx; i++ ) { if ( !strcmp( known_sfx [ i ].name, name ) ) { return ( &known_sfx [ i ] ); } } if ( !create ) { return ( NULL ); } /* find a free sfx */ for ( i = 0; i < num_sfx; i++ ) { if ( !known_sfx [ i ].name [ 0 ] ) { break; } } if ( i == num_sfx ) { if ( num_sfx == MAX_SFX ) { Com_Error( ERR_FATAL, "S_FindName: out of sfx_t" ); } num_sfx++; } if ( strlen( name ) >= MAX_QPATH - 1 ) { Com_Error( ERR_FATAL, "Sound name too long: %s", name ); } sfx = &known_sfx [ i ]; sfx->truename = NULL; strcpy( sfx->name, name ); sfx->registration_sequence = s_registration_sequence; return ( sfx ); } sfx_t * S_AliasName ( char *aliasname, char *truename ) { sfx_t *sfx; char *s; int i; s = Z_Malloc( MAX_QPATH ); strcpy( s, truename ); /* find a free sfx */ for ( i = 0; i < num_sfx; i++ ) { if ( !known_sfx [ i ].name [ 0 ] ) { break; } } if ( i == num_sfx ) { if ( num_sfx == MAX_SFX ) { Com_Error( ERR_FATAL, "S_FindName: out of sfx_t" ); } num_sfx++; } sfx = &known_sfx [ i ]; sfx->cache = NULL; strcpy( sfx->name, aliasname ); sfx->registration_sequence = s_registration_sequence; sfx->truename = s; return ( sfx ); } void S_BeginRegistration ( void ) { s_registration_sequence++; s_registering = true; } sfx_t * S_RegisterSound ( char *name ) { sfx_t *sfx; if ( !sound_started ) { return ( NULL ); } sfx = S_FindName( name, true ); sfx->registration_sequence = s_registration_sequence; if ( !s_registering ) { S_LoadSound( sfx ); } return ( sfx ); } void S_EndRegistration ( void ) { int i; sfx_t *sfx; /* free any sounds not from this registration sequence */ for ( i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++ ) { if ( !sfx->name [ 0 ] ) { continue; } if ( sfx->registration_sequence != s_registration_sequence ) { /* it is possible to have a leftover */ if ( sfx->cache ) { Z_Free( sfx->cache ); /* from a server that didn't finish loading */ } if ( sfx->truename ) { Z_Free( sfx->truename ); /* memleak fix from echon */ } sfx->cache = NULL; sfx->name [ 0 ] = 0; } } /* load everything in */ for ( i = 0, sfx = known_sfx; i < num_sfx; i++, sfx++ ) { if ( !sfx->name [ 0 ] ) { continue; } S_LoadSound( sfx ); } s_registering = false; } channel_t * S_PickChannel ( int entnum, int entchannel ) { int ch_idx; int first_to_die; int life_left; channel_t *ch; if ( entchannel < 0 ) { Com_Error( ERR_DROP, "S_PickChannel: entchannel<0" ); } /* Check for replacement sound, or find the best one to replace */ first_to_die = -1; life_left = 0x7fffffff; for ( ch_idx = 0; ch_idx < MAX_CHANNELS; ch_idx++ ) { /* channel 0 never overrides */ if ( ( entchannel != 0 ) && ( channels [ ch_idx ].entnum == entnum ) && ( channels [ ch_idx ].entchannel == entchannel ) ) { /* always override sound from same entity */ first_to_die = ch_idx; break; } /* don't let monster sounds override player sounds */ if ( ( channels [ ch_idx ].entnum == cl.playernum + 1 ) && ( entnum != cl.playernum + 1 ) && channels [ ch_idx ].sfx ) { continue; } if ( channels [ ch_idx ].end - paintedtime < life_left ) { life_left = channels [ ch_idx ].end - paintedtime; first_to_die = ch_idx; } } if ( first_to_die == -1 ) { return ( NULL ); } ch = &channels [ first_to_die ]; memset( ch, 0, sizeof ( *ch ) ); return ( ch ); } /* * Used for spatializing channels and autosounds */ void S_SpatializeOrigin ( vec3_t origin, float master_vol, float dist_mult, int *left_vol, int *right_vol ) { vec_t dot; vec_t dist; vec_t lscale, rscale, scale; vec3_t source_vec; if ( cls.state != ca_active ) { *left_vol = *right_vol = 255; return; } /* calculate stereo seperation and distance attenuation */ VectorSubtract( origin, listener_origin, source_vec ); dist = VectorNormalize( source_vec ); dist -= SOUND_FULLVOLUME; if ( dist < 0 ) { dist = 0; /* close enough to be at full volume */ } dist *= dist_mult; /* different attenuation levels */ dot = DotProduct( listener_right, source_vec ); if ( ( dma.channels == 1 ) || !dist_mult ) { /* no attenuation = no spatialization */ rscale = 1.0f; lscale = 1.0f; } else { rscale = 0.5f * ( 1.0f + dot ); lscale = 0.5f * ( 1.0f - dot ); } /* add in distance effect */ scale = ( 1.0f - dist ) * rscale; *right_vol = (int) ( master_vol * scale ); if ( *right_vol < 0 ) { *right_vol = 0; } scale = ( 1.0 - dist ) * lscale; *left_vol = (int) ( master_vol * scale ); if ( *left_vol < 0 ) { *left_vol = 0; } } void S_Spatialize ( channel_t *ch ) { vec3_t origin; /* anything coming from the view entity will always be full volume */ if ( ch->entnum == cl.playernum + 1 ) { ch->leftvol = ch->master_vol; ch->rightvol = ch->master_vol; return; } if ( ch->fixed_origin ) { VectorCopy( ch->origin, origin ); } else { CL_GetEntitySoundOrigin( ch->entnum, origin ); } S_SpatializeOrigin( origin, (float) ch->master_vol, ch->dist_mult, &ch->leftvol, &ch->rightvol ); } playsound_t * S_AllocPlaysound ( void ) { playsound_t *ps; ps = s_freeplays.next; if ( ps == &s_freeplays ) { return ( NULL ); /* no free playsounds, this results in stuttering an cracking */ } /* unlink from freelist */ ps->prev->next = ps->next; ps->next->prev = ps->prev; return ( ps ); } void S_FreePlaysound ( playsound_t *ps ) { /* unlink from channel */ ps->prev->next = ps->next; ps->next->prev = ps->prev; /* add to free list */ ps->next = s_freeplays.next; s_freeplays.next->prev = ps; ps->prev = &s_freeplays; s_freeplays.next = ps; } /* * Take the next playsound and begin it on the channel * This is never called directly by S_Play*, but only * by the update loop. */ void S_IssuePlaysound ( playsound_t *ps ) { channel_t *ch; sfxcache_t *sc; if ( !ps ) { return; } if ( s_show->value ) { Com_Printf( "Issue %i\n", ps->begin ); } /* pick a channel to play on */ ch = S_PickChannel( ps->entnum, ps->entchannel ); if ( !ch ) { S_FreePlaysound( ps ); return; } /* spatialize */ if ( ps->attenuation == ATTN_STATIC ) { ch->dist_mult = ps->attenuation * 0.001f; } else { ch->dist_mult = ps->attenuation * 0.0005f; } ch->master_vol = (int) ps->volume; ch->entnum = ps->entnum; ch->entchannel = ps->entchannel; ch->sfx = ps->sfx; VectorCopy( ps->origin, ch->origin ); ch->fixed_origin = ps->fixed_origin; S_Spatialize( ch ); ch->pos = 0; sc = S_LoadSound( ch->sfx ); ch->end = paintedtime + sc->length; /* free the playsound */ S_FreePlaysound( ps ); } struct sfx_s * S_RegisterSexedSound ( entity_state_t *ent, char *base ) { int n; char *p; int len; struct sfx_s *sfx; char model [ MAX_QPATH ]; char sexedFilename [ MAX_QPATH ]; char maleFilename [ MAX_QPATH ]; /* determine what model the client is using */ model [ 0 ] = 0; n = CS_PLAYERSKINS + ent->number - 1; if ( cl.configstrings [ n ] [ 0 ] ) { p = strchr( cl.configstrings [ n ], '\\' ); if ( p ) { p += 1; strcpy( model, p ); p = strchr( model, '/' ); if ( p ) { p [ 0 ] = 0; } } } /* if we can't figure it out, they're male */ if ( !model [ 0 ] ) { strcpy( model, "male" ); } /* see if we already know of the model specific sound */ Com_sprintf( sexedFilename, sizeof ( sexedFilename ), "#players/%s/%s", model, base + 1 ); sfx = S_FindName( sexedFilename, false ); if ( !sfx ) { /* no, so see if it exists */ len = FS_LoadFile( &sexedFilename [ 1 ], NULL ); if ( len != -1 ) { /* yes, close the file and register it */ sfx = S_RegisterSound( sexedFilename ); } else { /* no, revert to the male sound in the pak0.pak */ Com_sprintf( maleFilename, sizeof ( maleFilename ), "player/male/%s", base + 1 ); sfx = S_AliasName( sexedFilename, maleFilename ); } } return ( sfx ); } /* * Validates the parms and ques the sound up if pos is NULL, the sound * will be dynamically sourced from the entity Entchannel 0 will never * override a playing sound */ void S_StartSound ( vec3_t origin, int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation, float timeofs ) { sfxcache_t *sc; int vol; playsound_t *ps, *sort; int start; if ( !sound_started ) { return; } if ( !sfx ) { return; } if ( sfx->name [ 0 ] == '*' ) { sfx = S_RegisterSexedSound( &cl_entities [ entnum ].current, sfx->name ); } /* make sure the sound is loaded */ sc = S_LoadSound( sfx ); if ( !sc ) { return; /* couldn't load the sound's data */ } vol = fvol * 255; /* make the playsound_t */ ps = S_AllocPlaysound(); if ( !ps ) { return; } if ( origin ) { VectorCopy( origin, ps->origin ); ps->fixed_origin = true; } else { ps->fixed_origin = false; } ps->entnum = entnum; ps->entchannel = entchannel; ps->attenuation = attenuation; ps->volume = vol; ps->sfx = sfx; /* drift s_beginofs */ start = (int) ( cl.frame.servertime * 0.001f * dma.speed + s_beginofs ); if ( start < paintedtime ) { start = paintedtime; s_beginofs = (int) ( start - ( cl.frame.servertime * 0.001f * dma.speed ) ); } else if ( start > paintedtime + 0.3f * dma.speed ) { start = (int) ( paintedtime + 0.1f * dma.speed ); s_beginofs = (int) ( start - ( cl.frame.servertime * 0.001f * dma.speed ) ); } else { s_beginofs -= 10; } if ( !timeofs ) { ps->begin = paintedtime; } else { ps->begin = (int) ( start + timeofs * dma.speed ); } ps->volume = fvol * 255; /* sort into the pending sound list */ for ( sort = s_pendingplays.next; sort != &s_pendingplays && sort->begin < ps->begin; sort = sort->next ) { } ps->next = sort; ps->prev = sort->prev; ps->next->prev = ps; ps->prev->next = ps; } void S_StartLocalSound ( char *sound ) { sfx_t *sfx; if ( !sound_started ) { return; } sfx = S_RegisterSound( sound ); if ( !sfx ) { Com_Printf( "S_StartLocalSound: can't cache %s\n", sound ); return; } S_StartSound( NULL, cl.playernum + 1, 0, sfx, 1, 1, 0 ); } void S_ClearBuffer ( void ) { int clear; if ( !sound_started ) { return; } s_rawend = 0; if ( dma.samplebits == 8 ) { clear = 0x80; } else { clear = 0; } SNDDMA_BeginPainting(); if ( dma.buffer ) { int i; unsigned char *ptr = (unsigned char *) dma.buffer; i = dma.samples * dma.samplebits / 8; while ( i-- ) { *ptr = clear; ptr++; } } SNDDMA_Submit(); } void S_StopAllSounds ( void ) { int i; if ( !sound_started ) { return; } /* clear all the playsounds */ memset( s_playsounds, 0, sizeof ( s_playsounds ) ); s_freeplays.next = s_freeplays.prev = &s_freeplays; s_pendingplays.next = s_pendingplays.prev = &s_pendingplays; for ( i = 0; i < MAX_PLAYSOUNDS; i++ ) { s_playsounds [ i ].prev = &s_freeplays; s_playsounds [ i ].next = s_freeplays.next; s_playsounds [ i ].prev->next = &s_playsounds [ i ]; s_playsounds [ i ].next->prev = &s_playsounds [ i ]; } /* clear all the channels */ memset( channels, 0, sizeof ( channels ) ); S_ClearBuffer(); } /* * Entities with a ->sound field will generated looped sounds * that are automatically started, stopped, and merged together * as the entities are sent to the client */ void S_AddLoopSounds ( void ) { int i, j; int sounds [ MAX_EDICTS ]; int left, right, left_total, right_total; channel_t *ch; sfx_t *sfx; sfxcache_t *sc; int num; entity_state_t *ent; vec3_t origin; if ( cl_paused->value ) { return; } if ( cls.state != ca_active ) { return; } if ( !cl.sound_prepped ) { return; } for ( i = 0; i < cl.frame.num_entities; i++ ) { num = ( cl.frame.parse_entities + i ) & ( MAX_PARSE_ENTITIES - 1 ); ent = &cl_parse_entities [ num ]; sounds [ i ] = ent->sound; } for ( i = 0; i < cl.frame.num_entities; i++ ) { if ( !sounds [ i ] ) { continue; } sfx = cl.sound_precache [ sounds [ i ] ]; if ( !sfx ) { continue; /* bad sound effect */ } sc = sfx->cache; if ( !sc ) { continue; } num = ( cl.frame.parse_entities + i ) & ( MAX_PARSE_ENTITIES - 1 ); ent = &cl_parse_entities [ num ]; CL_GetEntitySoundOrigin( ent->number, origin ); /* find the total contribution of all sounds of this type */ S_SpatializeOrigin( ent->origin, 255.0f, SOUND_LOOPATTENUATE, &left_total, &right_total ); for ( j = i + 1; j < cl.frame.num_entities; j++ ) { if ( sounds [ j ] != sounds [ i ] ) { continue; } sounds [ j ] = 0; /* don't check this again later */ num = ( cl.frame.parse_entities + j ) & ( MAX_PARSE_ENTITIES - 1 ); ent = &cl_parse_entities [ num ]; S_SpatializeOrigin( ent->origin, 255.0f, SOUND_LOOPATTENUATE, &left, &right ); left_total += left; right_total += right; } if ( ( left_total == 0 ) && ( right_total == 0 ) ) { continue; /* not audible */ } /* allocate a channel */ ch = S_PickChannel( 0, 0 ); if ( !ch ) { return; } if ( left_total > 255 ) { left_total = 255; } if ( right_total > 255 ) { right_total = 255; } ch->leftvol = left_total; ch->rightvol = right_total; ch->autosound = true; /* remove next frame */ ch->sfx = sfx; /* Sometimes, the sc->length argument can become 0, and in that case we get a SIGFPE in the next modulo operation. The workaround checks for this situation and in that case, sets the pos and end parameters to 0. */ if ( sc->length == 0 ) { ch->pos = 0; ch->end = 0; } else { ch->pos = paintedtime % sc->length; ch->end = paintedtime + sc->length - ch->pos; } } } /* * Cinematic streaming and voice over network * This could be used for chat over network, but that * would be terrible slow. */ void S_RawSamples ( int samples, int rate, int width, int channels, byte *data ) { int i; int src, dst; float scale; if ( !sound_started ) { return; } if ( s_rawend < paintedtime ) { s_rawend = paintedtime; } scale = (float) rate / dma.speed; if ( ( channels == 2 ) && ( width == 2 ) ) { for ( i = 0; ; i++ ) { src = (int) ( i * scale ); if ( src >= samples ) { break; } dst = s_rawend & ( MAX_RAW_SAMPLES - 1 ); s_rawend++; s_rawsamples [ dst ].left = LittleShort( ( (short *) data ) [ src * 2 ] ) << 8; s_rawsamples [ dst ].right = LittleShort( ( (short *) data ) [ src * 2 + 1 ] ) << 8; } } else if ( ( channels == 1 ) && ( width == 2 ) ) { for ( i = 0; ; i++ ) { src = (int) ( i * scale ); if ( src >= samples ) { break; } dst = s_rawend & ( MAX_RAW_SAMPLES - 1 ); s_rawend++; s_rawsamples [ dst ].left = LittleShort( ( (short *) data ) [ src ] ) << 8; s_rawsamples [ dst ].right = LittleShort( ( (short *) data ) [ src ] ) << 8; } } else if ( ( channels == 2 ) && ( width == 1 ) ) { for ( i = 0; ; i++ ) { src = (int) ( i * scale ); if ( src >= samples ) { break; } dst = s_rawend & ( MAX_RAW_SAMPLES - 1 ); s_rawend++; s_rawsamples [ dst ].left = ( (char *) data ) [ src * 2 ] << 16; s_rawsamples [ dst ].right = ( (char *) data ) [ src * 2 + 1 ] << 16; } } else if ( ( channels == 1 ) && ( width == 1 ) ) { for ( i = 0; ; i++ ) { src = (int) ( i * scale ); if ( src >= samples ) { break; } dst = s_rawend & ( MAX_RAW_SAMPLES - 1 ); s_rawend++; s_rawsamples [ dst ].left = ( ( (byte *) data ) [ src ] - 128 ) << 16; s_rawsamples [ dst ].right = ( ( (byte *) data ) [ src ] - 128 ) << 16; } } } void GetSoundtime ( void ) { int samplepos; static int buffers; static int oldsamplepos; int fullsamples; fullsamples = dma.samples / dma.channels; /* it is possible to miscount buffers if it has wrapped twice between calls to S_Update. Oh well. This a hack around that. */ samplepos = SNDDMA_GetDMAPos(); if ( samplepos < oldsamplepos ) { buffers++; /* buffer wrapped */ if ( paintedtime > 0x40000000 ) { /* time to chop things off to avoid 32 bit limits */ buffers = 0; paintedtime = fullsamples; S_StopAllSounds(); } } oldsamplepos = samplepos; soundtime = buffers * fullsamples + samplepos / dma.channels; } /* * Called once each time through the main loop */ void S_Update ( vec3_t origin, vec3_t forward, vec3_t right, vec3_t up ) { int i; int total; channel_t *ch; unsigned endtime; int samps; if ( !sound_started ) { return; } /* if the laoding plaque is up, clear everything * out to make sure we aren't looping a dirty * dma buffer while loading */ if ( cls.disable_screen ) { S_ClearBuffer(); return; } /* rebuild scale tables if volume is modified */ if ( s_volume->modified ) { S_InitScaletable(); } VectorCopy( origin, listener_origin ); VectorCopy( forward, listener_forward ); VectorCopy( right, listener_right ); VectorCopy( up, listener_up ); /* update spatialization for dynamic sounds */ ch = channels; for ( i = 0; i < MAX_CHANNELS; i++, ch++ ) { if ( !ch->sfx ) { continue; } if ( ch->autosound ) { /* autosounds are regenerated fresh each frame */ memset( ch, 0, sizeof ( *ch ) ); continue; } S_Spatialize( ch ); /* respatialize channel */ if ( !ch->leftvol && !ch->rightvol ) { memset( ch, 0, sizeof ( *ch ) ); continue; } } /* add loopsounds */ S_AddLoopSounds(); /* debugging output */ if ( s_show->value ) { total = 0; ch = channels; for ( i = 0; i < MAX_CHANNELS; i++, ch++ ) { if ( ch->sfx && ( ch->leftvol || ch->rightvol ) ) { Com_Printf( "%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name ); total++; } } Com_Printf( "----(%i)---- painted: %i\n", total, paintedtime ); } /* stream music */ OGG_Stream(); /* mix some sound */ if ( !sound_started ) { return; } SNDDMA_BeginPainting(); if ( !dma.buffer ) { return; } /* Updates DMA time */ GetSoundtime(); if ( !soundtime ) { return; } /* check to make sure that we haven't overshot */ if ( paintedtime < soundtime ) { Com_DPrintf( "S_Update_ : overflow\n" ); paintedtime = soundtime; } /* mix ahead of current position */ endtime = (int) ( soundtime + s_mixahead->value * dma.speed ); /* mix to an even submission block size */ endtime = ( endtime + dma.submission_chunk - 1 ) & ~( dma.submission_chunk - 1 ); samps = dma.samples >> ( dma.channels - 1 ); if ( endtime - soundtime > samps ) { endtime = soundtime + samps; } S_PaintChannels( endtime ); SNDDMA_Submit(); } void S_Play ( void ) { int i; char name [ 256 ]; sfx_t *sfx; i = 1; while ( i < Cmd_Argc() ) { if ( !strrchr( Cmd_Argv( i ), '.' ) ) { strncpy( name, Cmd_Argv( i ), sizeof ( name ) - 5 ); strcat( name, ".wav" ); } else { strncpy( name, Cmd_Argv( i ), sizeof ( name ) - 1 ); } if ( strstr( name, ".." ) || ( name [ 0 ] == '/' ) || ( name [ 0 ] == '\\' ) ) { Com_Printf( "Bad filename %s\n", name ); return; } sfx = S_RegisterSound( name ); { S_StartSound( NULL, cl.playernum + 1, 0, sfx, 1.0, 1.0, 0 ); } i++; } } void S_SoundList ( void ) { int i; sfx_t *sfx; sfxcache_t *sc; int size, total; int numsounds; total = 0; numsounds = 0; for ( sfx = known_sfx, i = 0; i < num_sfx; i++, sfx++ ) { if ( !sfx->name [ 0 ] ) { continue; } sc = sfx->cache; if ( sc ) { size = sc->length * sc->width * ( sc->stereo + 1 ); total += size; Com_Printf( "%s(%2db) %8i : %s\n", sc->loopstart != -1 ? "L" : " ", sc->width * 8, size, sfx->name ); } else { if ( sfx->name [ 0 ] == '*' ) { Com_Printf( " placeholder : %s\n", sfx->name ); } else { Com_Printf( " not loaded : %s\n", sfx->name ); } } numsounds++; } Com_Printf( "Total resident: %i bytes (%.2f MB) in %d sounds\n", total, (float) total / 1024 / 1024, numsounds ); }