/quakeforge/trunk/libs/audio/renderer/snd_channels.c
C | 715 lines | 569 code | 87 blank | 59 comment | 136 complexity | 6249dec1ab6d6192d802ccc870ef24a3 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, AGPL-3.0, AGPL-1.0, Unlicense
- /*
- snd_dma.c
- main control for any streaming sound output device
- Copyright (C) 1996-1997 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:
- Free Software Foundation, Inc.
- 59 Temple Place - Suite 330
- Boston, MA 02111-1307, USA
- */
- #ifdef HAVE_CONFIG_H
- # include "config.h"
- #endif
- static __attribute__ ((used)) const char rcsid[] =
- "$Id: snd_dma.c 11380 2007-03-10 14:17:52Z taniwha $";
- #ifdef HAVE_STRING_H
- # include <string.h>
- #endif
- #ifdef HAVE_STRINGS_H
- # include <strings.h>
- #endif
- #ifdef HAVE_UNISTD_H
- # include <unistd.h>
- #endif
- #include <stdlib.h>
- #include "QF/bspfile.h"
- #include "QF/cmd.h"
- #include "QF/cvar.h"
- #include "QF/dstring.h"
- #include "QF/model.h"
- #include "QF/quakefs.h"
- #include "QF/sys.h"
- #include "snd_render.h"
- static channel_t *free_channels;
- channel_t snd_channels[MAX_CHANNELS];
- int snd_total_channels;
- static channel_t *ambient_channels[NUM_AMBIENTS];
- static channel_t *dynamic_channels;
- static channel_t *looped_dynamic_channels;
- static channel_t *static_channels[MAX_STATIC_CHANNELS];
- static int snd_num_statics;
- static qboolean snd_ambient = 1;
- static sfx_t *ambient_sfx[NUM_AMBIENTS];
- static vec_t sound_nominal_clip_dist = 1000.0;
- static vec3_t listener_origin;
- static vec3_t listener_forward;
- static vec3_t listener_right;
- static vec3_t listener_up;
- static cvar_t *snd_phasesep;
- static cvar_t *snd_volumesep;
- static cvar_t *ambient_fade;
- static cvar_t *ambient_level;
- static inline channel_t *
- unlink_channel (channel_t **_ch)
- {
- channel_t *ch = *_ch;
- *_ch = ch->next;
- ch->next = 0;
- return ch;
- }
- channel_t *
- SND_AllocChannel (void)
- {
- channel_t **free = &free_channels;
- channel_t *chan;
- while (*free) {
- if (!(*free)->sfx) // free channel
- break;
- if ((*free)->done) // mixer is finished with this channel
- break;
- if (!(*free)->stop)
- Sys_Error ("SND_AllocChannel: bogus channel free list");
- free = &(*free)->next;
- }
- if (!*free) {
- int num_free = 0;
- for (free = &free_channels; *free; free = &(*free)->next) {
- num_free++;
- }
- Sys_Printf ("SND_AllocChannel: out of channels. %d\n", num_free);
- return 0;
- }
- chan = *free;
- *free = chan->next;
- if (chan->sfx) {
- chan->sfx->release (chan->sfx);
- chan->sfx->close (chan->sfx);
- chan->sfx = 0; // make sure mixer doesn't use channel during memset
- }
- memset (chan, 0, sizeof (*chan));
- chan->next = 0;
- chan->sfx = 0;
- return chan;
- }
- void
- SND_ChannelStop (channel_t *chan)
- {
- /* if chan->next is set, then this channel may have already been freed.
- a rather serious bug as it will create a loop in the free list
- */
- if (chan->next)
- Sys_Error ("Stopping a freed channel");
- chan->stop = 1;
- chan->next = free_channels;
- free_channels = chan;
- }
- void
- SND_ScanChannels (int wait)
- {
- int i;
- channel_t *ch;
- int count = 0;
- if (!snd_shm || !snd_shm->speed)
- return;
- if (wait) {
- Sys_DPrintf ("scanning channels...\n");
- do {
- count = 0;
- for (i = 0; i < MAX_CHANNELS; i++) {
- ch = &snd_channels[i];
- if (!ch->sfx || ch->done)
- continue;
- ch->stop = 1;
- count++;
- }
- Sys_DPrintf ("count = %d\n", count);
- #ifdef HAVE_USLEEP
- usleep (1000);
- #endif
- } while (count);
- Sys_DPrintf ("scanning done.\n");
- } else {
- for (i = 0; i < MAX_CHANNELS; i++) {
- ch = &snd_channels[i];
- if (ch->sfx && ch->stop && !ch->done) {
- ch->done = 1;
- count++;
- }
- }
- //printf ("count: %d\n", count);
- }
- for (i = 0; i < MAX_CHANNELS; i++) {
- ch = &snd_channels[i];
- if (!ch->sfx || !ch->done)
- continue;
- ch->sfx->release (ch->sfx);
- ch->sfx->close (ch->sfx);
- ch->sfx = 0;
- }
- }
- void
- SND_StopAllSounds (void)
- {
- int i;
- snd_num_statics = 0;
- while (dynamic_channels)
- SND_ChannelStop (unlink_channel (&dynamic_channels));
- while (looped_dynamic_channels)
- SND_ChannelStop (unlink_channel (&looped_dynamic_channels));
- for (i = 0; i < NUM_AMBIENTS; i++) {
- if (ambient_channels[i])
- SND_ChannelStop (ambient_channels[i]);
- ambient_channels[i] = 0;
- }
- for (i = 0; i < MAX_STATIC_CHANNELS; i++) {
- if (static_channels[i])
- SND_ChannelStop (static_channels[i]);
- static_channels[i] = 0;
- }
- if (0) {
- channel_t *ch;
- Sys_Printf ("SND_StopAllSounds\n");
- for (i = 0, ch = free_channels; ch; ch = ch->next)
- i++;
- Sys_Printf (" free channels:%d\n", i);
- for (i = 0, ch = free_channels; ch; ch = ch->next)
- if (!ch->sfx || ch->done)
- i++;
- Sys_Printf (" truely free channels:%d\n", i);
- }
- }
- static void
- s_play_f (void)
- {
- dstring_t *name = dstring_new ();
- int i;
- static int hash = 345;
- sfx_t *sfx;
- i = 1;
- while (i < Cmd_Argc ()) {
- if (!strrchr (Cmd_Argv (i), '.')) {
- dsprintf (name, "%s.wav", Cmd_Argv (i));
- } else {
- dsprintf (name, "%s", Cmd_Argv (i));
- }
- sfx = SND_PrecacheSound (name->str);
- SND_StartSound (hash++, 0, sfx, listener_origin, 1.0, 1.0);
- i++;
- }
- dstring_delete (name);
- }
- static void
- s_playcenter_f (void)
- {
- dstring_t *name = dstring_new ();
- int i;
- sfx_t *sfx;
- i = 1;
- while (i < Cmd_Argc ()) {
- if (!strrchr (Cmd_Argv (i), '.')) {
- dsprintf (name, "%s.wav", Cmd_Argv (i));
- } else {
- dsprintf (name, "%s", Cmd_Argv (i));
- }
- sfx = SND_PrecacheSound (name->str);
- SND_StartSound (*snd_render_data.viewentity, 0, sfx, listener_origin,
- 1.0, 1.0);
- i++;
- }
- dstring_delete (name);
- }
- static void
- s_playvol_f (void)
- {
- dstring_t *name = dstring_new ();
- float vol;
- int i;
- static int hash = 543;
- sfx_t *sfx;
- i = 1;
- while (i < Cmd_Argc ()) {
- if (!strrchr (Cmd_Argv (i), '.')) {
- dsprintf (name, "%s.wav", Cmd_Argv (i));
- } else {
- dsprintf (name, "%s", Cmd_Argv (i));
- }
- sfx = SND_PrecacheSound (name->str);
- vol = atof (Cmd_Argv (i + 1));
- SND_StartSound (hash++, 0, sfx, listener_origin, vol, 1.0);
- i += 2;
- }
- dstring_delete (name);
- }
- static void
- s_channels_gamedir (int phase)
- {
- //FIXME for some reason, a gamedir change causes semi-random
- //"already released" cache errors. fortunatly, servers don't change
- //gamedir often, so I'll put this in the too-hard basket for now.
- if (phase) {
- ambient_sfx[AMBIENT_WATER] = SND_PrecacheSound ("ambience/water1.wav");
- ambient_sfx[AMBIENT_SKY] = SND_PrecacheSound ("ambience/wind2.wav");
- }
- }
- void
- SND_Channels_Init (void)
- {
- int i;
- snd_phasesep = Cvar_Get ("snd_phasesep", "0.0", CVAR_ARCHIVE, NULL,
- "max stereo phase separation in ms. 0.6 is for "
- "20cm head");
- snd_volumesep = Cvar_Get ("snd_volumesep", "1.0", CVAR_ARCHIVE, NULL,
- "max stereo volume separation. 1.0 is max");
- ambient_fade = Cvar_Get ("ambient_fade", "100", CVAR_NONE, NULL,
- "How quickly ambient sounds fade in or out");
- ambient_level = Cvar_Get ("ambient_level", "0.3", CVAR_NONE, NULL,
- "Ambient sounds' volume");
- Cmd_AddCommand ("play", s_play_f,
- "Play selected sound effect (play pathto/sound.wav)");
- Cmd_AddCommand ("playcenter", s_playcenter_f,
- "Play selected sound effect without 3D spatialization.");
- Cmd_AddCommand ("playvol", s_playvol_f, "Play selected sound effect at "
- "selected volume (playvol pathto/sound.wav num");
- for (i = 0; i < MAX_CHANNELS - 1; i++)
- snd_channels[i].next = &snd_channels[i + 1];
- free_channels = &snd_channels[0];
- snd_total_channels = MAX_CHANNELS;
- snd_num_statics = 0;
- QFS_GamedirCallback (s_channels_gamedir);
- }
- static channel_t *
- s_pick_channel (int entnum, int entchannel, int looped)
- {
- channel_t *ch, **_ch;
- // check for finished non-looped sounds
- for (_ch = &dynamic_channels; *_ch; ) {
- if (!(*_ch)->sfx || (*_ch)->done) {
- SND_ChannelStop (unlink_channel (_ch));
- continue;
- }
- _ch = &(*_ch)->next;
- }
- // non-looped sounds are used to stop looped sounds on an ent channel
- // also clean out any caught by SND_ScanChannels
- for (_ch = &looped_dynamic_channels; *_ch; ) {
- if (!(*_ch)->sfx || (*_ch)->done
- || ((*_ch)->entnum == entnum
- && ((*_ch)->entchannel == entchannel || entchannel == -1))) {
- SND_ChannelStop (unlink_channel (_ch));
- continue;
- }
- _ch = &(*_ch)->next;
- }
- _ch = looped ? &looped_dynamic_channels : &dynamic_channels;
- if ((ch = SND_AllocChannel ())) {
- ch->next = *_ch;
- *_ch = ch;
- }
- return ch;
- }
- void
- SND_AmbientOff (void)
- {
- snd_ambient = false;
- }
- void
- SND_AmbientOn (void)
- {
- snd_ambient = true;
- }
- static void
- s_updateAmbientSounds (void)
- {
- float vol;
- int ambient_channel;
- channel_t *chan;
- sfx_t *sfx;
- mleaf_t *l;
- if (!snd_ambient)
- return;
- // calc ambient sound levels
- if (!snd_render_data.worldmodel || !*snd_render_data.worldmodel)
- return;
- l = Mod_PointInLeaf (listener_origin, *snd_render_data.worldmodel);
- if (!l || !ambient_level->value) {
- // if we're not in a leaf (huh?) or ambients have been turned off,
- // stop all ambient channels.
- for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS;
- ambient_channel++) {
- chan = ambient_channels[ambient_channel];
- if (chan) {
- chan->master_vol = 0;
- chan->leftvol = chan->rightvol = chan->master_vol;
- }
- }
- return;
- }
- for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS;
- ambient_channel++) {
- sfx = ambient_sfx[ambient_channel];
- if (!sfx)
- continue;
- chan = ambient_channels[ambient_channel];
- if (!chan) {
- chan = ambient_channels[ambient_channel] = SND_AllocChannel ();
- if (!chan)
- continue;
- }
-
- if (!chan->sfx) {
- sfx = sfx->open (sfx);
- if (!sfx)
- continue;
- sfx->retain (sfx);
- } else {
- sfx = chan->sfx;
- //sfx->retain (sfx); //FIXME why is this necessary?
- }
- // sfx will be written to chan->sfx later to ensure mixer doesn't use
- // channel prematurely.
- vol = ambient_level->value * l->ambient_sound_level[ambient_channel];
- if (vol < 8)
- vol = 0;
- // don't adjust volume too fast
- if (chan->master_vol < vol) {
- chan->master_vol += *snd_render_data.host_frametime
- * ambient_fade->value;
- if (chan->master_vol > vol)
- chan->master_vol = vol;
- } else if (chan->master_vol > vol) {
- chan->master_vol -= *snd_render_data.host_frametime
- * ambient_fade->value;
- if (chan->master_vol < vol)
- chan->master_vol = vol;
- }
- chan->leftvol = chan->rightvol = chan->master_vol;
- chan->sfx = sfx;
- }
- }
- static void
- s_spatialize (channel_t *ch)
- {
- int phase; // in samples
- vec_t dist, dot, lscale, rscale, scale;
- vec3_t source_vec;
- // prepare to lerp from prev to next phase
- ch->oldphase = ch->phase;
- // anything coming from the view entity will always be full volume
- if (!snd_render_data.viewentity
- || ch->entnum == *snd_render_data.viewentity) {
- ch->leftvol = ch->master_vol;
- ch->rightvol = ch->master_vol;
- ch->phase = 0;
- return;
- }
- // calculate stereo seperation and distance attenuation
- VectorSubtract (ch->origin, listener_origin, source_vec);
- dist = VectorNormalize (source_vec) * ch->dist_mult;
- dot = DotProduct (listener_right, source_vec);
- if (snd_shm->channels == 1) {
- rscale = 1.0;
- lscale = 1.0;
- phase = 0;
- } else {
- rscale = 1.0 + dot * snd_volumesep->value;
- lscale = 1.0 - dot * snd_volumesep->value;
- phase = snd_phasesep->value * 0.001 * snd_shm->speed * dot;
- }
- // add in distance effect
- scale = (1.0 - dist) * rscale;
- ch->rightvol = (int) (ch->master_vol * scale);
- if (ch->rightvol < 0)
- ch->rightvol = 0;
- scale = (1.0 - dist) * lscale;
- ch->leftvol = (int) (ch->master_vol * scale);
- if (ch->leftvol < 0)
- ch->leftvol = 0;
- ch->phase = phase;
- }
- static inline int
- s_update_channel (channel_t *ch)
- {
- if (!ch->sfx)
- return 0;
- s_spatialize (ch); // respatialize channel
- if (!ch->leftvol && !ch->rightvol)
- return 0;
- return 1;
- }
- static void
- s_combine_channel (channel_t *combine, channel_t *ch)
- {
- combine->leftvol += ch->leftvol;
- combine->rightvol += ch->rightvol;
- ch->leftvol = ch->rightvol = 0;
- }
- void
- SND_SetListener (const vec3_t origin, const vec3_t forward, const vec3_t right,
- const vec3_t up)
- {
- int i, j;
- channel_t *combine, *ch;
- VectorCopy (origin, listener_origin);
- VectorCopy (forward, listener_forward);
- VectorCopy (right, listener_right);
- VectorCopy (up, listener_up);
- // update general area ambient sound sources
- s_updateAmbientSounds ();
- // update spatialization for dynamic sounds
- for (ch = dynamic_channels; ch; ch = ch->next)
- s_update_channel (ch);
- for (ch = looped_dynamic_channels; ch; ch = ch->next)
- s_update_channel (ch);
- // update spatialization for static sounds
- combine = 0;
- for (i = 0; i < snd_num_statics; i++) {
- ch = static_channels[i];
- if (!s_update_channel (ch))
- continue;
- // try to combine static sounds with a previous channel of the same
- // sound effect so we don't mix five torches every frame
- // see if it can just use the last one
- if (combine && combine->sfx == ch->sfx) {
- s_combine_channel (combine, ch);
- continue;
- }
- // search for one
- for (j = 0; j < i; j++) {
- combine = static_channels[j];
- if (combine->sfx == ch->sfx)
- break;
- }
- if (j == i) {
- combine = 0;
- } else {
- if (combine != ch)
- s_combine_channel (combine, ch);
- continue;
- }
- }
- }
- static int
- snd_check_channels (channel_t *target_chan, const channel_t *check,
- const sfx_t *osfx)
- {
- if (!check || check == target_chan)
- return 0;
- if (check->sfx->owner == osfx->owner && !check->pos) {
- int skip = rand () % (int) (0.01 * snd_shm->speed);
- target_chan->pos = -skip;
- return 1;
- }
- return 0;
- }
- void
- SND_StartSound (int entnum, int entchannel, sfx_t *sfx, const vec3_t origin,
- float fvol, float attenuation)
- {
- int vol;
- int looped;
- channel_t *target_chan, *check;
- sfx_t *osfx;
- if (!sfx || !snd_shm->speed)
- return;
- // pick a channel to play on
- looped = sfx->loopstart != (unsigned) -1;
- target_chan = s_pick_channel (entnum, entchannel, looped);
- if (!target_chan)
- return;
- vol = fvol * 255;
- // spatialize
- VectorCopy (origin, target_chan->origin);
- target_chan->dist_mult = attenuation / sound_nominal_clip_dist;
- target_chan->master_vol = vol;
- target_chan->entnum = entnum;
- target_chan->entchannel = entchannel;
- s_spatialize (target_chan);
- // new channel
- if (!(osfx = sfx->open (sfx))) {
- SND_ChannelStop (unlink_channel (looped ? &looped_dynamic_channels
- : &dynamic_channels));
- return;
- }
- target_chan->pos = 0;
- target_chan->end = 0;
- // if an identical sound has also been started this frame, offset the pos
- // a bit to keep it from just making the first one louder
- for (check = dynamic_channels; check; check = check->next)
- if (snd_check_channels (target_chan, check, osfx))
- break;
- for (check = looped_dynamic_channels; check; check = check->next)
- if (snd_check_channels (target_chan, check, osfx))
- break;
- if (!osfx->retain (osfx)) {
- SND_ChannelStop (unlink_channel (looped ? &looped_dynamic_channels
- : &dynamic_channels));
- return; // couldn't load the sound's data
- }
- target_chan->sfx = osfx;
- }
- static int
- s_check_stop (channel_t **_ch, int entnum, int entchannel)
- {
- if ((*_ch)->entnum == entnum && (*_ch)->entchannel == entchannel) {
- SND_ChannelStop (unlink_channel (_ch));
- return 1;
- }
- return 0;
- }
- void
- SND_StopSound (int entnum, int entchannel)
- {
- channel_t **_ch;
- for (_ch = &dynamic_channels; *_ch; )
- if (!s_check_stop (_ch, entnum, entchannel))
- _ch = &(*_ch)->next;
- for (_ch = &looped_dynamic_channels; *_ch; )
- if (!s_check_stop (_ch, entnum, entchannel))
- _ch = &(*_ch)->next;
- }
- void
- SND_StaticSound (sfx_t *sfx, const vec3_t origin, float vol,
- float attenuation)
- {
- channel_t *ss;
- sfx_t *osfx;
- if (!sfx)
- return;
- if (sfx->loopstart == (unsigned int) -1) {
- Sys_Printf ("Sound %s not looped\n", sfx->name);
- return;
- }
- if (!static_channels[snd_num_statics]) {
- if (!(static_channels[snd_num_statics] = SND_AllocChannel ())) {
- Sys_Printf ("ran out of channels\n");
- return;
- }
- }
- ss = static_channels[snd_num_statics];
- if (!(osfx = sfx->open (sfx)))
- return;
- VectorCopy (origin, ss->origin);
- ss->master_vol = vol;
- ss->dist_mult = (attenuation / 64) / sound_nominal_clip_dist;
- ss->end = 0;
- s_spatialize (ss);
- ss->oldphase = ss->phase;
- if (!osfx->retain (osfx))
- return;
- snd_num_statics++;
- ss->sfx = osfx;
- }
- void
- SND_LocalSound (const char *sound)
- {
- sfx_t *sfx;
- sfx = SND_PrecacheSound (sound);
- if (!sfx) {
- Sys_Printf ("S_LocalSound: can't cache %s\n", sound);
- return;
- }
- SND_StartSound (*snd_render_data.viewentity, -1, sfx, vec3_origin, 1, 1);
- }