/* 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. */ // snd_dma.c -- main control for any streaming sound output device #include "client.h" #include "../qcommon/redblack.h" #include "snd_loc.h" //FIXME OpenAL #ifdef _WIN32 extern qboolean ActiveApp; #endif void S_Play(void); void S_SoundList_f (void); void S_Update_(void); void S_StopAllSounds(void); /* Factor to control attenuation of audio. We'll divide all coordinates by this factor each time we update the source positions. OpenAL does provide a cleaner way to do this, but it changed recently. */ #define DISTANCE_FACTOR 50.0 // ======================================================================= // Internal sound data & structures // ======================================================================= // only begin attenuating sound volumes when outside the FULLVOLUME range static int s_registration_sequence; channel_t channels[MAX_CHANNELS]; static 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; // 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) static sfx_t known_sfx[MAX_SFX]; static int num_sfx; static struct rbtree *knownsounds; #define MAX_PLAYSOUNDS 128 static playsound_t s_playsounds[MAX_PLAYSOUNDS]; static playsound_t s_freeplays; playsound_t s_pendingplays; static int s_beginofs; cvar_t *s_volume; cvar_t *s_testsound; cvar_t *s_loadas8bit; cvar_t *s_khz; cvar_t *s_primary; cvar_t *s_openal_extensions; cvar_t *s_openal_eax; static cvar_t *s_show; static cvar_t *s_mixahead; static cvar_t *s_ambient; cvar_t *s_focusfree = &uninitialized_cvar; //cvar_t *s_dx8; int s_rawend; portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; #ifdef USE_OPENAL cvar_t *s_openal_volume; cvar_t *s_openal_device; static openal_listener_t s_openal_listener; static openal_channel_t s_openal_channels[MAX_CHANNELS]; static int s_openal_numChannels; static int s_openal_frameCount; #ifdef _WIN32 const GUID DSPROPSETID_EAX20_ListenerProperties = {0x306a6a8, 0xb224, 0x11d2, {0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22}}; const GUID DSPROPSETID_EAX20_BufferProperties = {0x306a6a7, 0xb224, 0x11d2, {0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22}}; #endif #endif // ==================================================================== // User-setable variables // ==================================================================== void S_SoundInfo_f(void) { if (!sound_started) { Com_Printf ("sound system not started\n", LOG_CLIENT); return; } Com_Printf("%5d stereo\n", LOG_CLIENT, dma.channels - 1); Com_Printf("%5d samples\n", LOG_CLIENT, dma.samples); Com_Printf("%5d samplepos\n", LOG_CLIENT, dma.samplepos); Com_Printf("%5d samplebits\n", LOG_CLIENT, dma.samplebits); Com_Printf("%5d submission_chunk\n", LOG_CLIENT, dma.submission_chunk); Com_Printf("%5d speed\n", LOG_CLIENT, dma.speed); Com_Printf("%p dma buffer\n", LOG_CLIENT, dma.buffer); } #ifdef USE_OPENAL static void S_OpenAL_AllocChannels (void) { openal_channel_t *ch; int i; for (i = 0, ch = s_openal_channels; i < MAX_CHANNELS; i++, ch++) { qalGenSources(1, &ch->sourceNum); if (qalGetError() != AL_NO_ERROR) break; s_openal_numChannels++; } } #endif /* ================ S_Init ================ */ void S_Init (int fullInit) { cvar_t *cv; Com_Printf("\n------- sound initialization -------\n", LOG_CLIENT|LOG_NOTICE); knownsounds = rbinit ((int (EXPORT *)(const void *, const void *))strcmp, 0); s_volume = Cvar_Get ("s_volume", "0.5", 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.2", CVAR_ARCHIVE); s_show = Cvar_Get ("s_show", "0", 0); s_ambient = Cvar_Get ("s_ambient", "1", 0); s_testsound = Cvar_Get ("s_testsound", "0", 0); s_primary = Cvar_Get ("s_primary", "0", CVAR_ARCHIVE); // win32 specific s_focusfree = Cvar_Get ("s_focusfree", "0", 0); //s_dx8 = Cvar_Get ("s_dx8", "0", CVAR_ARCHIVE); #ifdef USE_OPENAL s_openal_device = Cvar_Get ("s_openal_device", "", 0); s_openal_extensions = Cvar_Get ("s_openal_extensions", "1", 0); s_openal_eax = Cvar_Get ("s_openal_eax", "0", 0); s_openal_volume = Cvar_Get ("s_openal_volume", "1", 0); #endif s_volume = Cvar_Get ("s_volume", "0.5", CVAR_ARCHIVE); s_show = Cvar_Get ("s_show", "0", 0); s_ambient = Cvar_Get ("s_ambient", "1", 0); cv = Cvar_Get ("s_initsound", "1", 0); if (!cv->intvalue) Com_Printf ("not initializing.\n", LOG_CLIENT|LOG_NOTICE); else { if (cv->intvalue == 2) { #ifdef USE_OPENAL if (ALimp_Init ()) { sound_started = 1; Cmd_AddCommand("play", S_Play); Cmd_AddCommand("stopsound", S_StopAllSounds); S_OpenAL_AllocChannels (); S_StopAllSounds (); //inits freeplays S_StartLocalSound ("openalinit.wav"); } else { Com_Printf ("OpenAL failed to initialize; no sound available\n", LOG_CLIENT); } #else Com_Printf ("This binary was compiled without OpenAL support.\n", LOG_CLIENT); #endif } else { if (!SNDDMA_Init(fullInit)) return; Cmd_AddCommand("play", S_Play); Cmd_AddCommand("stopsound", S_StopAllSounds); Cmd_AddCommand("soundlist", S_SoundList_f); Cmd_AddCommand("soundinfo", S_SoundInfo_f); S_InitScaletable (); sound_started = 1; num_sfx = 0; soundtime = 0; paintedtime = 0; Com_Printf ("sound sampling rate: %i\n", LOG_CLIENT|LOG_NOTICE, dma.speed); S_StopAllSounds (); } } Com_Printf("------------------------------------\n", LOG_CLIENT|LOG_NOTICE); } // ======================================================================= // Shutdown sound engine // ======================================================================= #ifdef USE_OPENAL static void S_OpenAL_FreeChannels (void) { openal_channel_t *ch; int i; for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++) { qalDeleteSources(1, &ch->sourceNum); memset(ch, 0, sizeof(*ch)); } s_openal_numChannels = 0; } void S_OpenAL_FreeSounds (void) { sfx_t *sfx; int i; // Stop all sounds S_StopAllSounds(); // Free all sounds for (i = 0; i < num_sfx; i++) { sfx = &known_sfx[i]; qalDeleteBuffers(1, &sfx->bufferNum); } } #endif void S_Shutdown(void) { int i; sfx_t *sfx; if (!sound_started) return; #ifdef USE_OPENAL if (openal_active) { S_OpenAL_FreeSounds (); S_OpenAL_FreeChannels (); } #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); rbdelete (sfx->name, knownsounds); } memset (known_sfx, 0, sizeof(known_sfx)); rbdestroy (knownsounds); num_sfx = 0; sound_started = 0; #ifdef USE_OPENAL if (openal_active) { ALimp_Shutdown (); } else #endif { SNDDMA_Shutdown(); Cmd_RemoveCommand("soundlist"); Cmd_RemoveCommand("soundinfo"); } Cmd_RemoveCommand("play"); Cmd_RemoveCommand("stopsound"); } // ======================================================================= // Load a sound // ======================================================================= /* ================== S_FindName ================== */ sfx_t *S_FindName (char *name, qboolean create) { int i; sfx_t *sfx; void **data; if (!name) Com_Error (ERR_FATAL, "S_FindName: NULL\n"); // see if already loaded /*for (i=0 ; i < num_sfx ; i++) if (!strcmp(known_sfx[i].name, name)) { return &known_sfx[i]; }*/ data = rbfind (name, knownsounds); if (data) { sfx = *(sfx_t **)data; return sfx; } if (!name[0]) Com_Error (ERR_FATAL, "S_FindName: empty name\n"); 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++; } if (strlen(name) >= MAX_QPATH-1) Com_Error (ERR_FATAL, "Sound name too long: %s", name); sfx = &known_sfx[i]; sfx->cache = NULL; sfx->truename = NULL; strcpy (sfx->name, name); sfx->registration_sequence = s_registration_sequence; #ifdef USE_OPENAL sfx->loaded = false; sfx->samples = 0; sfx->rate = 0; sfx->format = 0; sfx->bufferNum = 0; #endif data = rbsearch (sfx->name, knownsounds); *data = sfx; return sfx; } /* ================== S_AliasName ================== */ sfx_t *S_AliasName (char *aliasname, char *truename) { sfx_t *sfx; void **data; int i; //s = Z_TagMalloc (MAX_QPATH, TAGMALLOC_CLIENT_SFX); //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]; //memset (sfx, 0, sizeof(*sfx)); sfx->cache = NULL; #ifdef USE_OPENAL sfx->loaded = false; sfx->samples = 0; sfx->rate = 0; sfx->format = 0; sfx->bufferNum = 0; #endif strcpy (sfx->name, aliasname); sfx->registration_sequence = s_registration_sequence; sfx->truename = CopyString (truename, TAGMALLOC_CLIENT_SFX); data = rbsearch (sfx->name, knownsounds); *data = sfx; 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) { #ifdef USE_OPENAL if (openal_active) { S_OpenAL_LoadSound (sfx); } else #endif { 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 if (sfx->truename) Z_Free (sfx->truename); // memleak fix from echon rbdelete (sfx->name, knownsounds); sfx->cache = NULL; sfx->name[0] = 0; #ifdef USE_OPENAL sfx->loaded = false; #endif //memset (sfx, 0, sizeof(*sfx)); } else { // make sure it is paged in #ifdef USE_OPENAL if (openal_active) continue; #endif 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; #ifdef USE_OPENAL if (openal_active) S_OpenAL_LoadSound (sfx); else #endif S_LoadSound (sfx); } s_registering = false; } //============================================================================= /* ================= 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++) { 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 (FLOAT_LT_ZERO(dist)) 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.0f - dist) * lscale; *left_vol = (int) (master_vol * scale); if (*left_vol < 0) *left_vol = 0; } void Snd_GetEntityOrigin (int entNum, vec3_t origin) { if (entNum == cl.playernum + 1) FastVectorCopy (listener_origin, *origin); else CL_GetEntityOrigin (entNum, origin); } /* ================= 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) { FastVectorCopy (ch->origin, origin); } else Snd_GetEntityOrigin (ch->entnum, origin); S_SpatializeOrigin (origin, (float)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->intvalue) Com_Printf ("Issue %i\n", LOG_CLIENT, 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; FastVectorCopy (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 (char *base, int entnum) { int n; int len; char *p; struct sfx_s *sfx; // FILE *f; 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; n = CS_PLAYERSKINS + entnum - 1; if (cl.configstrings[n][0]) { p = strchr(cl.configstrings[n], '\\'); if (p) { p += 1; Q_strncpy(model, p, sizeof(model)-1); 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 //FS_FOpenFile (&sexedFilename[1], &f, false); len = FS_LoadFile (&sexedFilename[1], NULL); if (len != -1) { // 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/male/%s", 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 ==================== */ #ifdef USE_OPENAL void S_OpenAL_StartSound (const vec3_t position, int entNum, int entChannel, sfx_t *sfx, float volume, float attenuation, int timeOfs) { playsound_t *ps, *sort; if (!sound_started) return; if (!sfx) return; if (sfx->name[0] == '*') sfx = S_RegisterSexedSound(sfx->name, entNum); // Make sure the sound is loaded if (!S_OpenAL_LoadSound(sfx)) return; // Allocate a playSound ps = S_AllocPlaysound (); if (!ps) { if (sfx->name[0] == '#') Com_DPrintf("Dropped sound %s\n", &sfx->name[1]); else Com_DPrintf("Dropped sound sound/%s\n", sfx->name); return; } volume *= volume; ps->sfx = sfx; ps->entnum = entNum; ps->entchannel = entChannel; if (position) { ps->fixed_origin = true; FastVectorCopy (*position, ps->origin); } else ps->fixed_origin = false; ps->volume = volume; ps->attenuation = attenuation; ps->begin = cl.time + timeOfs; // Sort into the pending playSounds 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; } #endif 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; #ifdef USE_OPENAL if (openal_active) { S_OpenAL_StartSound (origin, entnum, entchannel, sfx, fvol, attenuation, (int)(timeofs * 1000.0)); return; } #endif if (!sound_started) return; if (!sfx) return; if (sfx->name[0] == '*') sfx = S_RegisterSexedSound(sfx->name, entnum); //Com_DPrintf ("S_StartSound: %s\n", sfx->name); // make sure the sound is loaded sc = S_LoadSound (sfx); if (!sc) return; // couldn't load the sound's data vol = (int)(fvol*255); // make the playsound_t ps = S_AllocPlaysound (); if (!ps) return; if (origin) { FastVectorCopy (*origin, ps->origin); ps->fixed_origin = true; } else ps->fixed_origin = false; ps->entnum = entnum; ps->entchannel = entchannel; ps->attenuation = attenuation; 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; } /* ================== 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", LOG_CLIENT, sound); return; } //r1: don't use attenuation calculations on local sounds S_StartSound (NULL, cl.playernum + 1, 0, sfx, 1, ATTN_NONE, 0); } /* ================== S_ClearBuffer ================== */ void S_ClearBuffer (void) { int clear; if (!sound_started) return; #ifdef USE_OPENAL if (openal_active) { return; } #endif 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 (); } #ifdef USE_OPENAL static void S_OpenAL_StopChannel (openal_channel_t *ch) { ch->sfx = NULL; qalSourceStop(ch->sourceNum); qalSourcei(ch->sourceNum, AL_BUFFER, 0); } #endif /* ================== 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]; } #ifdef USE_OPENAL if (openal_active) { openal_channel_t *ch; // Stop all the channels for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++) { if (!ch->sfx) continue; S_OpenAL_StopChannel(ch); } // Reset frame count s_openal_frameCount = 0; } #endif // clear all the channels memset(channels, 0, sizeof(channels)); 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; entity_state_t *ent; vec3_t origin; if (cl_paused->intvalue) 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]; //cmodel sound fix /*if (ent->solid == 31) { cmodel_t *model; model = cl.model_clip[ent->modelindex]; //not loaded (deferred?) if (!model) continue; origin[0] = ent->origin[0]+0.5f*(model->mins[0]+model->maxs[0]); origin[1] = ent->origin[1]+0.5f*(model->mins[1]+model->maxs[1]); origin[2] = ent->origin[2]+0.5f*(model->mins[2]+model->maxs[2]); } else { VectorCopy (ent->origin, origin); }*/ Snd_GetEntityOrigin (ent->number, origin); // find the total contribution of all sounds of this type S_SpatializeOrigin (origin, 255.0f, SOUND_LOOPATTENUATE, &left_total, &right_total); for (j=i+1 ; jorigin, 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; 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) { int i; int src, dst; float scale; if (!sound_started) return; #ifdef USE_OPENAL if (openal_active) return; #endif 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.0f) { // optimized case for (i=0 ; i= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = LittleShort(((int16 *)data)[src*2]) << 8; s_rawsamples[dst].right = LittleShort(((int16 *)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(((int16 *)data)[src]) << 8; s_rawsamples[dst].right = LittleShort(((int16 *)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; } } } /* ================= S_FreeChannels ================= */ #ifdef USE_OPENAL static void S_OpenAL_PlayChannel (openal_channel_t *ch, sfx_t *sfx) { ch->sfx = sfx; qalSourcei(ch->sourceNum, AL_BUFFER, sfx->bufferNum); qalSourcei(ch->sourceNum, AL_LOOPING, ch->loopSound); qalSourcei(ch->sourceNum, AL_SOURCE_RELATIVE, AL_FALSE); qalSourcePlay(ch->sourceNum); } openal_channel_t *S_OpenAL_PickChannel (int entNum, int entChannel) { openal_channel_t *ch; int i; int firstToDie = -1; int oldestTime = cl.time; if (entNum < 0 || entChannel < 0) Com_Error (ERR_DROP, "S_PickChannel: bad entNum"); for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++) { // Don't let game sounds override streaming sounds if (ch->streaming) continue; // Check if this channel is active if (!ch->sfx) { // Free channel firstToDie = i; break; } // Channel 0 never overrides if (entChannel != 0 && (ch->entNum == entNum && ch->entChannel == entChannel)) { // Always override sound from same entity firstToDie = i; break; } // Don't let monster sounds override player sounds if (entNum != cl.playernum+1 && ch->entNum == cl.playernum+1) continue; // Replace the oldest sound if (ch->startTime < oldestTime) { oldestTime = ch->startTime; firstToDie = i; } } if (firstToDie == -1) return NULL; ch = &s_openal_channels[firstToDie]; ch->entNum = entNum; ch->entChannel = entChannel; ch->startTime = cl.time; // Make sure this channel is stopped qalSourceStop(ch->sourceNum); qalSourcei(ch->sourceNum, AL_BUFFER, 0); return ch; } static void S_OpenAL_SpatializeChannel (openal_channel_t *ch) { vec3_t position; // Update position and velocity if (ch->entNum == cl.playernum+1 || !ch->distanceMult) { qalSourcefv(ch->sourceNum, AL_POSITION, s_openal_listener.position); //qalSourcefv(ch->sourceNum, AL_VELOCITY, s_openal_listener.velocity); } else { if (ch->fixedPosition) { qalSource3f(ch->sourceNum, AL_POSITION, ch->position[1], ch->position[2], -ch->position[0]); //qalSource3f(ch->sourceNum, AL_VELOCITY, 0, 0, 0); } else { if (ch->loopSound) Snd_GetEntityOrigin (ch->loopNum, position); else Snd_GetEntityOrigin (ch->entNum, position); qalSource3f(ch->sourceNum, AL_POSITION, position[1], position[2], -position[0]); //qalSource3f(ch->sourceNum, AL_VELOCITY, velocity[1], velocity[2], -velocity[0]); } } // Update min/max distance if (ch->distanceMult) qalSourcef(ch->sourceNum, AL_REFERENCE_DISTANCE, 240.0f * ch->distanceMult); else qalSourcef(ch->sourceNum, AL_REFERENCE_DISTANCE, 8192); qalSourcef(ch->sourceNum, AL_MAX_DISTANCE, 8192); // Update volume and rolloff factor qalSourcef(ch->sourceNum, AL_GAIN, s_openal_volume->value * ch->volume); qalSourcef(ch->sourceNum, AL_ROLLOFF_FACTOR, 1.0f); } static void S_OpenAL_AddLoopingSounds (void) { entity_state_t *ent; sfx_t *sfx; // openal_sfx_t asfx; openal_channel_t *ch; int i, j; if (cls.state != ca_active || cl_paused->intvalue) return; for (i = 0; i < cl.frame.num_entities; i++) { ent = &cl_parse_entities[(cl.frame.parse_entities+i) & (MAX_PARSE_ENTITIES-1)]; if (!ent->sound) continue; sfx = cl.sound_precache[ent->sound]; if (!sfx || !sfx->loaded) continue; // Bad sound effect // If this entity is already playing the same sound effect on an // active channel, then simply update it for (j = 0, ch = s_openal_channels; j < s_openal_numChannels; j++, ch++) { if (ch->sfx != sfx) continue; if (!ch->loopSound) continue; if (ch->loopNum != ent->number) continue; if (ch->loopFrame + 1 != s_openal_frameCount) continue; ch->loopFrame = s_openal_frameCount; break; } if (j != s_openal_numChannels) continue; // Otherwise pick a channel and start the sound effect ch = S_OpenAL_PickChannel(0, 0); if (!ch) { if (sfx->name[0] == '#') Com_DPrintf("Dropped sound %s\n", &sfx->name[1]); else Com_DPrintf("Dropped sound sound/%s\n", sfx->name); continue; } ch->loopSound = true; ch->loopNum = ent->number; ch->loopFrame = s_openal_frameCount; ch->fixedPosition = false; ch->volume = 1.0f; ch->distanceMult = 0.3f;//1.0f / ATTN_STATIC; S_OpenAL_SpatializeChannel(ch); S_OpenAL_PlayChannel(ch, sfx); } } static int S_OpenAL_ChannelState (openal_channel_t *ch) { int state; qalGetSourcei(ch->sourceNum, AL_SOURCE_STATE, &state); return state; } static void S_OpenAL_IssuePlaySounds (void) { playsound_t *ps; openal_channel_t *ch; for (;;) { ps = s_pendingplays.next; if (ps == &s_pendingplays) break; // No more pending playSounds if (ps->begin > cl.time) break; // No more pending playSounds this frame // Pick a channel and start the sound effect ch = S_OpenAL_PickChannel(ps->entnum, ps->entchannel); if (!ch) { if (ps->sfx->name[0] == '#') Com_DPrintf("Dropped sound %s\n", &ps->sfx->name[1]); else Com_DPrintf("Dropped sound sound/%s\n", ps->sfx->name); S_FreePlaysound (ps); continue; } ch->loopSound = false; ch->fixedPosition = ps->fixed_origin; FastVectorCopy (ps->origin, ch->position); ch->volume = ps->volume; if (ps->attenuation != ATTN_NONE) ch->distanceMult = 1.0f / ps->attenuation; else ch->distanceMult = 0.0f; S_OpenAL_SpatializeChannel(ch); S_OpenAL_PlayChannel(ch, ps->sfx); // Free the playSound S_FreePlaysound(ps); } } //============================================================================= int EXPORT CL_PMpointcontents (vec3_t point); void S_Update_OpenAL (vec3_t position, const vec3_t velocity, const vec3_t at, const vec3_t up) { unsigned eaxEnv; openal_channel_t *ch; int i, total = 0; // Bump frame count s_openal_frameCount++; // Set up listener VectorSet(s_openal_listener.position, position[1], position[2], -position[0]); //VectorSet(s_openal_listener.velocity, velocity[1], velocity[2], -velocity[0]); s_openal_listener.orientation[0] = at[1]; s_openal_listener.orientation[1] = -at[2]; s_openal_listener.orientation[2] = -at[0]; s_openal_listener.orientation[3] = up[1]; s_openal_listener.orientation[4] = -up[2]; s_openal_listener.orientation[5] = -up[0]; qalListenerfv(AL_POSITION, s_openal_listener.position); //qalListenerfv(AL_VELOCITY, s_openal_listener.velocity); qalListenerfv(AL_ORIENTATION, s_openal_listener.orientation); #ifdef _WIN32 qalListenerf(AL_GAIN, (ActiveApp) ? s_volume->value : 0); #else qalListenerf(AL_GAIN, s_volume->value); #endif // Set state qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); //qalDistanceModel(AL_INVERSE_DISTANCE); //qalDopplerFactor(s_dopplerFactor->value); //qalDopplerVelocity(s_dopplerVelocity->value); // If EAX is enabled, apply listener environmental effects #ifdef _WIN32 if (alConfig.eax) { if (cls.state != ca_active) { eaxEnv = EAX_ENVIRONMENT_GENERIC; } else { if (CL_PMpointcontents (position) & MASK_WATER) eaxEnv = EAX_ENVIRONMENT_UNDERWATER; else eaxEnv = EAX_ENVIRONMENT_GENERIC; } if (eaxEnv != alConfig.eaxState) { alConfig.eaxState = eaxEnv; qalEAXSet (&DSPROPSETID_EAX20_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT | DSPROPERTY_EAXLISTENER_IMMEDIATE, 0, &eaxEnv, sizeof(eaxEnv)); } } #endif // Stream background track //S_StreamBackgroundTrack(); // Add looping sounds if (s_ambient->intvalue) S_OpenAL_AddLoopingSounds(); // Issue playSounds S_OpenAL_IssuePlaySounds(); // Update spatialization for all sounds for (i = 0, ch = s_openal_channels; i < s_openal_numChannels; i++, ch++) { if (!ch->sfx) continue; // Not active // Check for stop if (ch->loopSound) { if (ch->loopFrame != s_openal_frameCount) { S_OpenAL_StopChannel(ch); continue; } } else { if (S_OpenAL_ChannelState(ch) == AL_STOPPED) { S_OpenAL_StopChannel(ch); continue; } } // Respatialize channel S_OpenAL_SpatializeChannel(ch); if (s_show->intvalue) { if (ch->sfx->name[0] == '#') Com_Printf("%2i: %s\n", LOG_CLIENT, i+1, &ch->sfx->name[1]); else Com_Printf("%2i: sound/%s\n", LOG_CLIENT, i+1, ch->sfx->name); } total++; } if (s_show->intvalue) Com_Printf("--- ( %i ) ---\n", LOG_CLIENT, total); } #endif /* ============ 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; } #ifdef USE_OPENAL if (openal_active) { S_Update_OpenAL (origin, vec3_origin, forward, up); return; } #endif FastVectorCopy (*origin, listener_origin); FastVectorCopy (*forward, listener_forward); FastVectorCopy (*right, listener_right); FastVectorCopy (*up, listener_up); // rebuild scale tables if volume is modified if (s_volume->modified) S_InitScaletable (); //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 if (s_ambient->intvalue) S_AddLoopSounds (); // // debugging output // if (s_show->intvalue) { total = 0; ch = channels; for (i=0 ; isfx && (ch->leftvol || ch->rightvol) ) { Com_Printf ("%3i %3i %s\n", LOG_CLIENT, ch->leftvol, ch->rightvol, ch->sfx->name); total++; } Com_Printf ("----(%i)---- painted: %i\n", LOG_CLIENT, total, paintedtime); } // 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) { uint32 endtime; int32 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 = (int)(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 (iname[0] = '\0'; } i++; } } void S_SoundList_f (void) { int i; sfx_t *sfx; sfxcache_t *sc; int size, total; int numsounds; total = 0; numsounds = 0; for (sfx=known_sfx, i=0 ; iname[0]) continue; sc = sfx->cache; if (sc) { size = sc->length*sc->width*(sc->stereo+1); total += size; Com_Printf("%s(%2db) %8i : %s\n", LOG_CLIENT, sc->loopstart != -1 ? "L" : " ", sc->width*8, size, sfx->name); } else { if (sfx->name[0] == '*') Com_Printf(" placeholder : %s\n", LOG_CLIENT, sfx->name); else Com_Printf(" not loaded : %s\n", LOG_CLIENT, sfx->name); } numsounds++; } Com_Printf ("Total resident: %i bytes (%.2f MB) in %d sounds\n", LOG_CLIENT, total, (float)total / 1024 / 1024, numsounds); }