/* =========================================================================== Copyright (C) 1997-2001 Id Software, Inc. This file is part of Quake 2 source code. Quake 2 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 2 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 2 source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // snd_dma.c -- main control for any streaming sound output device #include "client.h" #include "snd_loc.h" void S_Play(void); void S_SoundList(void); void S_Update_(); void S_StopAllSounds(void); // ======================================================================= // Internal sound data & structures // ======================================================================= // 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; qboolean s_registering; int soundtime; // sample PAIRS int paintedtime; // sample PAIRS // 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_show; cvar_t *s_mixahead; cvar_t *s_primary; cvar_t *s_usedefaultsound; // Knightmare added #ifdef OGG_SUPPORT cvar_t *s_musicvolume; #endif 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("0x%x dma buffer\n", dma.buffer); } /* ================ S_Init ================ */ void S_Init (void) { cvar_t *cv; Com_Printf("\n------- Sound Initialization -------\n"); cv = Cvar_Get ("s_initsound", "1", 0); Cvar_SetDescription ("s_initsound", "Enables the initialization of the sound subsystem."); if (!cv->value) Com_Printf ("not initializing.\n"); else { s_volume = Cvar_Get ("s_volume", "1.0", CVAR_ARCHIVE); Cvar_SetDescription ("s_volume", "Sets the sound volume (normalized). 0 = mute, 1.0 = max."); s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); Cvar_SetDescription ("s_khz", "Sets the sound sampling rate in KHz."); s_loadas8bit = Cvar_Get ("s_loadas8bit", "0", CVAR_ARCHIVE); Cvar_SetDescription ("s_loadas8bit", "Forces 8-bit low quality sounds when set to 1."); s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); Cvar_SetDescription ("s_mixahead", "Sets sound mix-ahead time in seconds."); s_show = Cvar_Get ("s_show", "0", 0); Cvar_SetDescription ("s_show", "Enables sound debugging output."); s_testsound = Cvar_Get ("s_testsound", "0", 0); Cvar_SetDescription ("s_testsound", "Enables generated test sound."); s_primary = Cvar_Get ("s_primary", "0", CVAR_ARCHIVE); // win32 specific Cvar_SetDescription ("s_primary", "Enables primary sound buffer. Set to 1 for better performance, or 0 for improved compatibility."); s_usedefaultsound = Cvar_Get ("s_usedefaultsound", "0", CVAR_ARCHIVE); // Knightmare added Cvar_SetDescription ("s_usedefaultsound", "Enables use of a default generated tone when a sound file can't be loaded."); #ifdef OGG_SUPPORT s_musicvolume = Cvar_Get ("s_musicvolume", "1.0", CVAR_ARCHIVE); // Q2E Cvar_SetDescription ("s_musicvolume", "Sets the music volume (normalized). 0 = mute, 1.0 = max."); #endif Cmd_AddCommand("play", S_Play); Cmd_AddCommand("stopsound", S_StopAllSounds); Cmd_AddCommand("soundlist", S_SoundList); Cmd_AddCommand("soundinfo", S_SoundInfo_f); #ifdef OGG_SUPPORT Cmd_AddCommand("ogg_restart", S_OGG_Restart); #endif 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 (); } #ifdef OGG_SUPPORT S_OGG_Init(); #endif Com_Printf("------------------------------------\n"); } // ======================================================================= // Shutdown sound engine // ======================================================================= void S_Shutdown (void) { int i; sfx_t *sfx; if (!sound_started) return; #ifdef OGG_SUPPORT S_OGG_Shutdown(); #endif SNDDMA_Shutdown(); sound_started = 0; Cmd_RemoveCommand("play"); Cmd_RemoveCommand("stopsound"); Cmd_RemoveCommand("soundlist"); Cmd_RemoveCommand("soundinfo"); #ifdef OGG_SUPPORT Cmd_RemoveCommand("ogg_restart"); #endif // 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 (sfx, 0, sizeof(*sfx)); } num_sfx = 0; } // ======================================================================= // Load a sound // ======================================================================= /* ================== S_FindName ================== */ 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]) // registration_sequence < s_registration_sequence) 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]; memset (sfx, 0, sizeof(*sfx)); // strncpy (sfx->name, name); Q_strncpyz (sfx->name, sizeof(sfx->name), name); sfx->registration_sequence = s_registration_sequence; return sfx; } /* ================== S_AliasName ================== */ sfx_t *S_AliasName (char *aliasname, char *truename) { sfx_t *sfx; char *s; int i; s = Z_Malloc (MAX_QPATH); // strncpy (s, truename); Q_strncpyz (s, MAX_QPATH, 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]; memset (sfx, 0, sizeof(*sfx)); // strncpy (sfx->name, aliasname); Q_strncpyz (sfx->name, sizeof(sfx->name), aliasname); sfx->registration_sequence = s_registration_sequence; sfx->truename = s; return sfx; } /* ===================== S_BeginRegistration ===================== */ void S_BeginRegistration (void) { s_registration_sequence++; s_registering = true; } /* ================== S_RegisterSound ================== */ 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; } /* ===================== S_EndRegistration ===================== */ void S_EndRegistration (void) { int i; sfx_t *sfx; int size; // 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) { // don't need this sound if (sfx->cache) // it is possible to have a leftover Z_Free (sfx->cache); // from a server that didn't finish loading memset (sfx, 0, sizeof(*sfx)); } else { // make sure it is paged in if (sfx->cache) { size = sfx->cache->length*sfx->cache->width; Com_PageInMemory ((byte *)sfx->cache, size); } } } // 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; // Knightmare added // S_EndRegistration is only called while the refresh is prepped // during a sound resart, so we can use this to restart the music track if (cls.state == ca_active && cl.refresh_prepped && !CDAudio_Active()) CL_PlayBackgroundTrack (); } //============================================================================= /* ================= S_PickChannel ================= */ 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++) { #ifdef OGG_SUPPORT // Don't let game sounds override streaming sounds if (channels[ch_idx].streaming) // Q2E continue; #endif if (entchannel != 0 // channel 0 never overrides && 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; } /* ================= S_SpatializeOrigin 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.0; lscale = 1.0; } else { rscale = 0.5 * (1.0 + dot); lscale = 0.5*(1.0 - dot); } // add in distance effect scale = (1.0 - 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; } /* ================= S_Spatialize ================= */ 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, ch->master_vol, ch->dist_mult, &ch->leftvol, &ch->rightvol); } /* ================= S_AllocPlaysound ================= */ playsound_t *S_AllocPlaysound (void) { playsound_t *ps; ps = s_freeplays.next; if (ps == &s_freeplays) return NULL; // no free playsounds // unlink from freelist ps->prev->next = ps->next; ps->next->prev = ps->prev; return ps; } /* ================= S_FreePlaysound ================= */ 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; } /* =============== S_IssuePlaysound 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 (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) if (!ch || !ps->sfx || !ps->sfx->name[0]) // pointer / buffer check { S_FreePlaysound (ps); return; } // spatialize if (ps->attenuation == ATTN_STATIC) ch->dist_mult = ps->attenuation * 0.001; else ch->dist_mult = ps->attenuation * 0.0005; ch->master_vol = 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; // crashes here // free the playsound S_FreePlaysound (ps); } struct sfx_s *S_RegisterSexedSound (entity_state_t *ent, char *base) { int n; char *p; struct sfx_s *sfx; fileHandle_t f; char model[MAX_QPATH]; char sexedFilename[MAX_QPATH]; char maleFilename[MAX_QPATH]; // determine what model the client is using model[0] = 0; // Knightmare- BIG UGLY HACK for old connected to server using old protocol // Changed config strings require different parsing if ( LegacyProtocol() ) n = OLD_CS_PLAYERSKINS + ent->number - 1; else n = CS_PLAYERSKINS + ent->number - 1; if (cl.configstrings[n][0]) { p = strchr(cl.configstrings[n], '\\'); if (p) { p += 1; // strncpy(model, p); Q_strncpyz (model, sizeof(model), p); p = strchr(model, '/'); if (p) *p = 0; } } // if we can't figure it out, they're male if (!model[0]) // strncpy(model, "male"); Q_strncpyz (model, sizeof(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 FS_FOpenFile (&sexedFilename[1], &f, FS_READ); if (f) { // yes, close the file and register it FS_FCloseFile (f); sfx = S_RegisterSound (sexedFilename); } else { // no, revert to the male sound in the pak0.pak Com_sprintf (maleFilename, sizeof(maleFilename), "player/%s/%s", "male", base+1); sfx = S_AliasName (sexedFilename, maleFilename); } } return sfx; } // ======================================================================= // Start a sound effect // ======================================================================= /* ==================== S_StartSound 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 = cl.frame.servertime * 0.001 * dma.speed + s_beginofs; if (start < paintedtime) { start = paintedtime; s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed); } else if (start > paintedtime + 0.3 * dma.speed) { start = paintedtime + 0.1 * dma.speed; s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed); } else { s_beginofs-=10; } if (!timeofs) ps->begin = paintedtime; else ps->begin = start + timeofs * dma.speed; // 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; } /* ================== S_StartLocalSound ================== */ 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); } /* ================== S_ClearBuffer ================== */ 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) memset(dma.buffer, clear, dma.samples * dma.samplebits/8); SNDDMA_Submit (); } /* ================== S_StopAllSounds ================== */ 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 ; inext = &s_playsounds[i]; s_playsounds[i].next->prev = &s_playsounds[i]; } // clear all the channels memset(channels, 0, sizeof(channels)); #ifdef OGG_SUPPORT // Stop background track S_StopBackgroundTrack (); // Q2E #endif S_ClearBuffer (); } /* ================== S_AddLoopSounds 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; float dist_mult; vec3_t origin_v; // Knightmare added entity_state_t *ent; if (cl_paused->value) return; if (cls.state != ca_active) return; if (!cl.sound_prepped) return; for (i=0 ; isound; } for (i=0 ; icache; if (!sc) continue; num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1); ent = &cl_parse_entities[num]; #ifdef NEW_ENTITY_STATE_MEMBERS #ifdef LOOP_SOUND_ATTENUATION if (ent->attenuation <= 0.0f || ent->attenuation == ATTN_STATIC) dist_mult = SOUND_LOOPATTENUATE; else dist_mult = ent->attenuation * 0.0005; #else dist_mult = SOUND_LOOPATTENUATE; #endif #else dist_mult = SOUND_LOOPATTENUATE; #endif // Knightmare- find correct origin for bmodels without origin brushes if (ent->solid == 31) // special value for bmodels { cmodel_t *cmodel; qboolean outofbounds = false; int k; cmodel = cl.model_clip[ent->modelindex]; if (cmodel) { for (k=0; k<3; k++) if (ent->origin[k] < cmodel->mins[k] || ent->origin[k] > cmodel->maxs[k]) outofbounds = true; } if (cmodel && outofbounds) { for (k=0; k<3; k++) origin_v[k] = ent->origin[k] + 0.5 * (cmodel->mins[k] + cmodel->maxs[k]); } else VectorCopy (ent->origin, origin_v); } else VectorCopy (ent->origin, origin_v); // find the total contribution of all sounds of this type S_SpatializeOrigin (origin_v, 255.0, dist_mult, // SOUND_LOOPATTENUATE // was ent->origin &left_total, &right_total); for (j=i+1; jorigin &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; ch->pos = paintedtime % sc->length; ch->end = paintedtime + sc->length - ch->pos; } } //============================================================================= /* ============ S_RawSamples Cinematic streaming and voice over network ============ */ void S_RawSamples (int samples, int rate, int width, int channels, byte *data, qboolean music) { int i; int src, dst; float scale; int snd_vol; if (!sound_started) return; #ifdef OGG_SUPPORT if (music) snd_vol = (int)(s_musicvolume->value * 256); else #endif snd_vol = (int)(s_volume->value * 256); if (s_rawend < paintedtime) s_rawend = paintedtime; scale = (float)rate / dma.speed; //Com_Printf ("%i < %i < %i\n", soundtime, paintedtime, s_rawend); if (channels == 2 && width == 2) { if (scale == 1.0) { // optimized case for (i=0 ; i= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = LittleShort(((short *)data)[src*2]) * snd_vol; // << 8; s_rawsamples[dst].right = LittleShort(((short *)data)[src*2+1]) * snd_vol; // << 8; } } } else if (channels == 1 && width == 2) { for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = LittleShort(((short *)data)[src]) * snd_vol; // << 8; s_rawsamples[dst].right = LittleShort(((short *)data)[src]) * snd_vol; // << 8; } } else if (channels == 2 && width == 1) { for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ( ((char *)data)[src*2] << 8 ) * snd_vol; // << 16; s_rawsamples[dst].right = ( ((char *)data)[src*2+1] << 8 ) * snd_vol; // << 16; } } else if (channels == 1 && width == 1) { for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ( (((byte *)data)[src]-128) << 8 ) * snd_vol; // << 16; s_rawsamples[dst].right = ( (((byte *)data)[src]-128) << 8 ) * snd_vol; // << 16; } } } //============================================================================= /* ============ S_Update 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; channel_t *combine; 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 (); //if (s_musicvolume->modified) // S_InitMusicScaletable (); VectorCopy(origin, listener_origin); VectorCopy(forward, listener_forward); VectorCopy(right, listener_right); VectorCopy(up, listener_up); combine = NULL; // update spatialization for dynamic sounds ch = channels; for (i=0 ; isfx) 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 ; isfx && (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); } #ifdef OGG_SUPPORT S_UpdateBackgroundTrack (); #endif // mix some sound S_Update_(); } 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. 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; } void S_Update_(void) { unsigned endtime; int samps; if (!sound_started) return; SNDDMA_BeginPainting (); if (!dma.buffer) return; // Updates DMA time GetSoundtime(); // 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 = soundtime + s_mixahead->value * dma.speed; //endtime = (soundtime + 4096) & ~4095; // 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 (); } /* =============================================================================== console functions =============================================================================== */ void S_Play(void) { int i; char name[256]; sfx_t *sfx; i = 1; while (iregistration_sequence) continue; sc = sfx->cache; if (sc) { size = sc->length*sc->width*(sc->stereo+1); total += size; if (sc->loopstart >= 0) Com_Printf ("L"); else Com_Printf (" "); Com_Printf("(%2db) %6i : %s\n",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); } } Com_Printf ("Total resident: %i\n", total); }