2007-03-17 03:14:41 +00:00
|
|
|
/*
|
|
|
|
snd_dma.c
|
|
|
|
|
|
|
|
main control for any streaming sound output device
|
|
|
|
|
|
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
2022-06-04 03:10:09 +00:00
|
|
|
Copyright (C) 2003-2022 Bill Currie <bill@taniwha.org>
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
#ifdef HAVE_STRING_H
|
|
|
|
# include <string.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_STRINGS_H
|
|
|
|
# include <strings.h>
|
|
|
|
#endif
|
2007-04-07 07:44:07 +00:00
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
# include <unistd.h>
|
|
|
|
#endif
|
2007-03-17 03:14:41 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include "QF/bspfile.h"
|
|
|
|
#include "QF/cmd.h"
|
|
|
|
#include "QF/cvar.h"
|
|
|
|
#include "QF/dstring.h"
|
|
|
|
#include "QF/model.h"
|
2007-03-23 12:33:04 +00:00
|
|
|
#include "QF/quakefs.h"
|
2022-06-04 03:10:09 +00:00
|
|
|
#include "QF/set.h"
|
2007-03-17 03:14:41 +00:00
|
|
|
#include "QF/sys.h"
|
|
|
|
|
2022-02-28 07:59:38 +00:00
|
|
|
#include "QF/scene/transform.h"
|
|
|
|
|
2011-07-23 06:56:22 +00:00
|
|
|
#include "snd_internal.h"
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
#define SND_STATIC_ID -1
|
|
|
|
|
|
|
|
typedef struct entchan_s {
|
|
|
|
int id; // entity id
|
|
|
|
int channel; // per-entity sound channel
|
|
|
|
} entchan_t;
|
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
typedef struct spacial_s {
|
|
|
|
vec3_t origin; //!< origin of sound effect
|
|
|
|
vec_t dist_mult; //!< distance multiplier (attenuation/clip)
|
|
|
|
float volume; //!< 0-1 overall channel volume
|
|
|
|
} spacial_t;
|
|
|
|
|
2007-03-17 03:14:41 +00:00
|
|
|
int snd_total_channels;
|
2022-06-04 03:10:09 +00:00
|
|
|
channel_t snd_channels[MAX_CHANNELS];
|
|
|
|
static entchan_t snd_entity_channels[MAX_CHANNELS];
|
2022-06-04 07:17:43 +00:00
|
|
|
static spacial_t snd_spacialization[MAX_CHANNELS];
|
2022-06-04 03:10:09 +00:00
|
|
|
static int snd_free_channels[MAX_CHANNELS];
|
|
|
|
static int snd_num_free_channels;
|
|
|
|
/* Dynamic channels are (usually) short sound bytes, never looped. They do not
|
|
|
|
* override other dynamic channels even for the same entity channel. However,
|
|
|
|
* they DO override (stop) looped channels on the same entity channel.
|
|
|
|
*/
|
|
|
|
static set_bits_t dynamic_channel_bits[SET_WORDS_STATIC (MAX_CHANNELS)];
|
|
|
|
static set_t dynamic_channels = SET_STATIC_ARRAY (dynamic_channel_bits);
|
|
|
|
/* Looped channels are sounds that automatically repeat until they are stopped.
|
|
|
|
* They can be stopped via SND_ChannelStop or by starting another sound
|
|
|
|
* (dynamic or looped) on the same entity channel.
|
|
|
|
*/
|
|
|
|
static set_bits_t looped_channel_bits[SET_WORDS_STATIC (MAX_CHANNELS)];
|
|
|
|
static set_t looped_channels = SET_STATIC_ARRAY (looped_channel_bits);
|
|
|
|
static set_bits_t static_channel_bits[SET_WORDS_STATIC (MAX_CHANNELS)];
|
|
|
|
static set_t static_channels = SET_STATIC_ARRAY (static_channel_bits);
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
static channel_t *ambient_channels[NUM_AMBIENTS];
|
|
|
|
|
|
|
|
static qboolean snd_ambient = 1;
|
|
|
|
static sfx_t *ambient_sfx[NUM_AMBIENTS];
|
|
|
|
|
|
|
|
static vec_t sound_nominal_clip_dist = 1000.0;
|
|
|
|
|
2022-02-28 07:59:38 +00:00
|
|
|
static vec4f_t listener_origin;
|
|
|
|
static vec4f_t listener_forward;
|
|
|
|
static vec4f_t listener_right;
|
|
|
|
static vec4f_t listener_up;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
[cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.
As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.
The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).
While not used yet (partly due to working out the design), cvars can
have a validation function.
Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.
nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-23 03:22:45 +00:00
|
|
|
static float snd_phasesep;
|
|
|
|
static cvar_t snd_phasesep_cvar = {
|
|
|
|
.name = "snd_phasesep",
|
|
|
|
.description =
|
|
|
|
"max stereo phase separation in ms. 0.6 is for 20cm head",
|
|
|
|
.default_value = "0.0",
|
|
|
|
.flags = CVAR_ARCHIVE,
|
|
|
|
.value = { .type = &cexpr_float, .value = &snd_phasesep },
|
|
|
|
};
|
|
|
|
static float snd_volumesep;
|
|
|
|
static cvar_t snd_volumesep_cvar = {
|
|
|
|
.name = "snd_volumesep",
|
|
|
|
.description =
|
|
|
|
"max stereo volume separation. 1.0 is max",
|
|
|
|
.default_value = "1.0",
|
|
|
|
.flags = CVAR_ARCHIVE,
|
|
|
|
.value = { .type = &cexpr_float, .value = &snd_volumesep },
|
|
|
|
};
|
|
|
|
static int snd_swapchannelside;
|
|
|
|
static cvar_t snd_swapchannelside_cvar = {
|
|
|
|
.name = "snd_swapchannelside",
|
|
|
|
.description =
|
|
|
|
"Toggle swapping of left and right channels",
|
|
|
|
.default_value = "0",
|
|
|
|
.flags = CVAR_ARCHIVE,
|
|
|
|
.value = { .type = &cexpr_int, .value = &snd_swapchannelside },
|
|
|
|
};
|
|
|
|
static float ambient_fade;
|
|
|
|
static cvar_t ambient_fade_cvar = {
|
|
|
|
.name = "ambient_fade",
|
|
|
|
.description =
|
|
|
|
"How quickly ambient sounds fade in or out",
|
|
|
|
.default_value = "100",
|
|
|
|
.flags = CVAR_NONE,
|
|
|
|
.value = { .type = &cexpr_float, .value = &ambient_fade },
|
|
|
|
};
|
|
|
|
static float ambient_level;
|
|
|
|
static cvar_t ambient_level_cvar = {
|
|
|
|
.name = "ambient_level",
|
|
|
|
.description =
|
|
|
|
"Ambient sounds' volume",
|
|
|
|
.default_value = "0.3",
|
|
|
|
.flags = CVAR_NONE,
|
|
|
|
.value = { .type = &cexpr_float, .value = &ambient_level },
|
|
|
|
};
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
static void
|
|
|
|
snd_free_channel (channel_t *ch)
|
2007-03-27 06:15:57 +00:00
|
|
|
{
|
2022-06-05 07:57:13 +00:00
|
|
|
sfxbuffer_t *buffer = ch->buffer;
|
|
|
|
ch->buffer = 0;
|
2022-06-04 03:10:09 +00:00
|
|
|
ch->stop = 0;
|
|
|
|
ch->done = 0;
|
|
|
|
int chan_ind = ch - snd_channels;
|
|
|
|
if (snd_num_free_channels >= MAX_CHANNELS) {
|
|
|
|
Sys_Error ("snd_num_free_channels: free channel list overflow");
|
|
|
|
}
|
|
|
|
snd_free_channels[snd_num_free_channels++] = chan_ind;
|
|
|
|
snd_entity_channels[chan_ind] = (entchan_t) {};
|
2022-06-05 07:57:13 +00:00
|
|
|
|
|
|
|
if (buffer) {
|
|
|
|
buffer->close (buffer);
|
|
|
|
}
|
2007-03-27 06:15:57 +00:00
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
channel_t *
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_AllocChannel (snd_t *snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2007-03-18 10:32:01 +00:00
|
|
|
channel_t *chan;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-05 07:57:13 +00:00
|
|
|
Sys_MaskPrintf (SYS_snd, "SND_AllocChannel: free channels: %d\n",
|
|
|
|
snd_num_free_channels);
|
2022-06-04 03:10:09 +00:00
|
|
|
if (!snd_num_free_channels) {
|
|
|
|
Sys_MaskPrintf (SYS_warn, "SND_AllocChannel: out of channels.\n");
|
|
|
|
return 0;
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
2022-06-04 03:10:09 +00:00
|
|
|
int chan_ind = snd_free_channels[--snd_num_free_channels];
|
|
|
|
chan = &snd_channels[chan_ind];
|
2007-03-18 10:32:01 +00:00
|
|
|
|
|
|
|
memset (chan, 0, sizeof (*chan));
|
|
|
|
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_ChannelStop (snd_t *snd, channel_t *chan)
|
2007-03-18 10:32:01 +00:00
|
|
|
{
|
2022-06-05 07:57:13 +00:00
|
|
|
if (!chan->buffer) {
|
2022-06-04 03:10:09 +00:00
|
|
|
Sys_MaskPrintf (SYS_warn, "Sound: stop called on invalid channel\n");
|
|
|
|
}
|
2007-03-27 06:15:57 +00:00
|
|
|
chan->stop = 1;
|
2022-06-04 03:10:09 +00:00
|
|
|
int chan_ind = chan - snd_channels;
|
|
|
|
set_remove (&dynamic_channels, chan_ind);
|
|
|
|
set_remove (&looped_channels, chan_ind);
|
|
|
|
set_remove (&static_channels, chan_ind);
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
2007-03-18 11:20:47 +00:00
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_ScanChannels (snd_t *snd, int wait)
|
2007-03-18 11:20:47 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
channel_t *ch;
|
|
|
|
int count = 0;
|
|
|
|
|
2021-06-23 15:04:02 +00:00
|
|
|
if (!snd || !snd->speed)
|
2007-05-21 11:20:36 +00:00
|
|
|
return;
|
|
|
|
|
2007-04-07 07:44:07 +00:00
|
|
|
if (wait) {
|
2022-06-05 08:39:44 +00:00
|
|
|
Sys_MaskPrintf (SYS_snd, "scanning channels...\n");
|
2007-04-07 07:44:07 +00:00
|
|
|
do {
|
|
|
|
count = 0;
|
|
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
ch = &snd_channels[i];
|
2022-06-05 07:57:13 +00:00
|
|
|
if (!ch->buffer || ch->done)
|
2007-04-07 07:44:07 +00:00
|
|
|
continue;
|
|
|
|
ch->stop = 1;
|
|
|
|
count++;
|
|
|
|
}
|
2022-06-05 08:39:44 +00:00
|
|
|
Sys_MaskPrintf (SYS_snd, "count = %d\n", count);
|
2007-04-07 07:44:07 +00:00
|
|
|
#ifdef HAVE_USLEEP
|
|
|
|
usleep (1000);
|
|
|
|
#endif
|
|
|
|
} while (count);
|
2022-06-05 08:39:44 +00:00
|
|
|
Sys_MaskPrintf (SYS_snd, "scanning done.\n");
|
2007-04-07 07:44:07 +00:00
|
|
|
} else {
|
|
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
ch = &snd_channels[i];
|
2022-06-05 07:57:13 +00:00
|
|
|
if (ch->buffer && ch->stop && !ch->done) {
|
2007-04-07 07:44:07 +00:00
|
|
|
ch->done = 1;
|
|
|
|
count++;
|
|
|
|
}
|
2007-03-18 11:20:47 +00:00
|
|
|
}
|
2007-04-07 07:44:07 +00:00
|
|
|
//printf ("count: %d\n", count);
|
2007-03-18 11:20:47 +00:00
|
|
|
}
|
2007-05-07 14:03:36 +00:00
|
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
ch = &snd_channels[i];
|
2022-06-05 07:57:13 +00:00
|
|
|
if (!ch->buffer || !ch->done)
|
2007-05-07 14:03:36 +00:00
|
|
|
continue;
|
2022-06-05 09:11:32 +00:00
|
|
|
snd_free_channel (ch);
|
2007-05-07 14:03:36 +00:00
|
|
|
}
|
2022-06-05 09:11:32 +00:00
|
|
|
Sys_MaskPrintf (SYS_snd, "SND_ScanChannels: free channels: %d\n",
|
|
|
|
snd_num_free_channels);
|
2007-03-18 11:20:47 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 07:18:18 +00:00
|
|
|
void
|
|
|
|
SND_FinishChannels (void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
channel_t *ch;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
ch = &snd_channels[i];
|
|
|
|
ch->done = ch->stop = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-03-17 03:14:41 +00:00
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_StopAllSounds (snd_t *snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2022-06-04 03:10:09 +00:00
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
if (set_is_member (&dynamic_channels, i)
|
|
|
|
|| set_is_member (&looped_channels, i)
|
|
|
|
|| set_is_member (&static_channels, i)) {
|
|
|
|
SND_ChannelStop (snd, &snd_channels[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
set_empty (&dynamic_channels);
|
|
|
|
set_empty (&looped_channels);
|
|
|
|
set_empty (&static_channels);
|
|
|
|
for (int i = 0; i < NUM_AMBIENTS; i++) {
|
2007-03-27 06:15:57 +00:00
|
|
|
if (ambient_channels[i])
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_ChannelStop (snd, ambient_channels[i]);
|
2007-03-18 10:32:01 +00:00
|
|
|
ambient_channels[i] = 0;
|
2007-03-27 06:15:57 +00:00
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-06-23 15:04:02 +00:00
|
|
|
s_play_f (void *_snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2021-06-23 15:04:02 +00:00
|
|
|
snd_t *snd = _snd;
|
2007-03-17 03:14:41 +00:00
|
|
|
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));
|
|
|
|
}
|
2021-06-23 15:04:02 +00:00
|
|
|
sfx = SND_PrecacheSound (snd, name->str);
|
2022-03-30 14:38:01 +00:00
|
|
|
SND_StartSound (snd, hash++, 0, sfx, listener_origin, 1.0, 1.0);
|
2007-03-17 03:14:41 +00:00
|
|
|
i++;
|
|
|
|
}
|
|
|
|
dstring_delete (name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-06-23 15:04:02 +00:00
|
|
|
s_playcenter_f (void *_snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2021-06-23 15:04:02 +00:00
|
|
|
snd_t *snd = _snd;
|
2007-03-17 03:14:41 +00:00
|
|
|
dstring_t *name = dstring_new ();
|
|
|
|
int i;
|
|
|
|
sfx_t *sfx;
|
2012-12-17 06:07:49 +00:00
|
|
|
int viewent = 0;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2012-12-17 06:07:49 +00:00
|
|
|
if (snd_render_data.viewentity)
|
|
|
|
viewent = *snd_render_data.viewentity;
|
|
|
|
|
|
|
|
for (i = 1; i < Cmd_Argc (); i++) {
|
2007-03-17 03:14:41 +00:00
|
|
|
if (!strrchr (Cmd_Argv (i), '.')) {
|
|
|
|
dsprintf (name, "%s.wav", Cmd_Argv (i));
|
|
|
|
} else {
|
|
|
|
dsprintf (name, "%s", Cmd_Argv (i));
|
|
|
|
}
|
2021-06-23 15:04:02 +00:00
|
|
|
sfx = SND_PrecacheSound (snd, name->str);
|
2022-03-30 14:38:01 +00:00
|
|
|
SND_StartSound (snd, viewent, 0, sfx, listener_origin, 1.0, 1.0);
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
dstring_delete (name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-06-23 15:04:02 +00:00
|
|
|
s_playvol_f (void *_snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2021-06-23 15:04:02 +00:00
|
|
|
snd_t *snd = _snd;
|
2007-03-17 03:14:41 +00:00
|
|
|
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));
|
|
|
|
}
|
2021-06-23 15:04:02 +00:00
|
|
|
sfx = SND_PrecacheSound (snd, name->str);
|
2007-03-17 03:14:41 +00:00
|
|
|
vol = atof (Cmd_Argv (i + 1));
|
2022-03-30 14:38:01 +00:00
|
|
|
SND_StartSound (snd, hash++, 0, sfx, listener_origin, vol, 1.0);
|
2007-03-17 03:14:41 +00:00
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
dstring_delete (name);
|
|
|
|
}
|
|
|
|
|
2007-03-23 12:33:04 +00:00
|
|
|
static void
|
2022-06-04 07:06:04 +00:00
|
|
|
s_channels_gamedir (int phase, void *_snd)
|
2007-03-23 12:33:04 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2007-03-17 03:14:41 +00:00
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_Channels_Init (snd_t *snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
[cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.
As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.
The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).
While not used yet (partly due to working out the design), cvars can
have a validation function.
Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.
nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-23 03:22:45 +00:00
|
|
|
Cvar_Register (&snd_phasesep_cvar, 0, 0);
|
|
|
|
Cvar_Register (&snd_volumesep_cvar, 0, 0);
|
|
|
|
Cvar_Register (&snd_swapchannelside_cvar, 0, 0);
|
|
|
|
Cvar_Register (&ambient_fade_cvar, 0, 0);
|
|
|
|
Cvar_Register (&ambient_level_cvar, 0, 0);
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2021-06-23 15:04:02 +00:00
|
|
|
Cmd_AddDataCommand ("play", s_play_f, snd,
|
|
|
|
"Play selected sound effect (play pathto/sound.wav)");
|
|
|
|
Cmd_AddDataCommand ("playcenter", s_playcenter_f, snd,
|
|
|
|
"Play selected sound effect without 3D "
|
|
|
|
"spatialization.");
|
|
|
|
Cmd_AddDataCommand ("playvol", s_playvol_f, snd,
|
|
|
|
"Play selected sound effect at selected volume "
|
|
|
|
"(playvol pathto/sound.wav num");
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
snd_free_channels[i] = MAX_CHANNELS - 1 - i;
|
|
|
|
}
|
|
|
|
snd_num_free_channels = MAX_CHANNELS;
|
2007-03-18 10:32:01 +00:00
|
|
|
snd_total_channels = MAX_CHANNELS;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 07:06:04 +00:00
|
|
|
QFS_GamedirCallback (s_channels_gamedir, snd);
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static channel_t *
|
2021-06-23 15:04:02 +00:00
|
|
|
s_pick_channel (snd_t *snd, int entnum, int entchannel, int looped)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2007-03-21 12:56:43 +00:00
|
|
|
// check for finished non-looped sounds
|
2022-06-04 03:10:09 +00:00
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
channel_t *ch = &snd_channels[i];
|
|
|
|
if (set_is_member (&dynamic_channels, i)) {
|
|
|
|
if (ch->done) {
|
|
|
|
// mixer is done with the channel, it can be freed
|
|
|
|
snd_free_channel (ch);
|
|
|
|
set_remove (&dynamic_channels, i);
|
|
|
|
}
|
|
|
|
} else if (set_is_member (&looped_channels, i)) {
|
|
|
|
// non-looped sounds are used to stop looped sounds on an entity
|
|
|
|
// channel also clean out any caught by SND_ScanChannels
|
|
|
|
entchan_t *entchan = &snd_entity_channels[i];
|
|
|
|
if (entchan->id == entnum
|
|
|
|
&& (entchan->channel == entchannel || entchannel == -1)) {
|
|
|
|
// the mixer is still using the channel, so send a request
|
|
|
|
// for it to stopp
|
|
|
|
SND_ChannelStop (snd, ch);
|
|
|
|
}
|
2007-05-08 09:33:24 +00:00
|
|
|
}
|
|
|
|
}
|
2007-03-18 10:32:01 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
return SND_AllocChannel (snd);
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_AmbientOff (snd_t *snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
|
|
|
snd_ambient = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_AmbientOn (snd_t *snd)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
|
|
|
snd_ambient = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-06-23 15:04:02 +00:00
|
|
|
s_updateAmbientSounds (snd_t *snd, const byte *ambient_sound_level)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
|
|
|
float vol;
|
|
|
|
int ambient_channel;
|
|
|
|
|
|
|
|
if (!snd_ambient)
|
|
|
|
return;
|
|
|
|
// calc ambient sound levels
|
[cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.
As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.
The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).
While not used yet (partly due to working out the design), cvars can
have a validation function.
Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.
nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-23 03:22:45 +00:00
|
|
|
if (!ambient_sound_level || !ambient_level) {
|
2007-03-17 03:14:41 +00:00
|
|
|
// 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++) {
|
2022-06-05 07:57:13 +00:00
|
|
|
channel_t *chan = ambient_channels[ambient_channel];
|
2007-03-24 11:11:19 +00:00
|
|
|
if (chan) {
|
2022-06-04 07:17:43 +00:00
|
|
|
int chan_ind = chan - snd_channels;
|
|
|
|
spacial_t *spacial = &snd_spacialization[chan_ind];
|
|
|
|
spacial->volume = 0;
|
|
|
|
chan->leftvol = chan->rightvol = spacial->volume;
|
2007-03-24 10:13:10 +00:00
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS;
|
|
|
|
ambient_channel++) {
|
2022-06-05 07:57:13 +00:00
|
|
|
sfx_t *sfx = ambient_sfx[ambient_channel];
|
2007-03-24 10:13:10 +00:00
|
|
|
if (!sfx)
|
2007-03-17 03:14:41 +00:00
|
|
|
continue;
|
2007-03-18 10:32:01 +00:00
|
|
|
|
2022-06-05 07:57:13 +00:00
|
|
|
channel_t *chan = ambient_channels[ambient_channel];
|
2007-03-24 10:13:10 +00:00
|
|
|
if (!chan) {
|
2021-06-23 15:04:02 +00:00
|
|
|
chan = ambient_channels[ambient_channel] = SND_AllocChannel (snd);
|
2007-03-24 10:13:10 +00:00
|
|
|
if (!chan)
|
|
|
|
continue;
|
|
|
|
}
|
2012-05-21 23:23:22 +00:00
|
|
|
|
2022-06-05 07:57:13 +00:00
|
|
|
sfxbuffer_t *buffer;
|
|
|
|
if (!chan->buffer) {
|
|
|
|
buffer = sfx->open (sfx);
|
|
|
|
if (!buffer)
|
2007-03-24 10:13:10 +00:00
|
|
|
continue;
|
2007-03-18 10:45:29 +00:00
|
|
|
} else {
|
2022-06-05 07:57:13 +00:00
|
|
|
buffer = chan->buffer;
|
2007-03-24 10:13:10 +00:00
|
|
|
//sfx->retain (sfx); //FIXME why is this necessary?
|
2007-03-18 10:45:29 +00:00
|
|
|
}
|
2022-06-05 07:57:13 +00:00
|
|
|
// buffer will be written to chan->buffer later to ensure mixer
|
|
|
|
// doesn't use channel prematurely.
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
int chan_ind = chan - snd_channels;
|
|
|
|
spacial_t *spacial = &snd_spacialization[chan_ind];
|
|
|
|
|
2022-06-03 07:54:57 +00:00
|
|
|
vol = ambient_level * ambient_sound_level[ambient_channel] * (1/255.0);
|
|
|
|
if (vol < 8/255.0)
|
2007-03-17 03:14:41 +00:00
|
|
|
vol = 0;
|
|
|
|
|
|
|
|
// don't adjust volume too fast
|
2022-06-03 07:54:57 +00:00
|
|
|
float fade = ambient_fade * (1/255.0);
|
2022-06-04 07:17:43 +00:00
|
|
|
if (spacial->volume < vol) {
|
|
|
|
spacial->volume += *snd_render_data.host_frametime * fade;
|
|
|
|
if (spacial->volume > vol)
|
|
|
|
spacial->volume = vol;
|
|
|
|
} else if (spacial->volume > vol) {
|
|
|
|
spacial->volume -= *snd_render_data.host_frametime * fade;
|
|
|
|
if (spacial->volume < vol)
|
|
|
|
spacial->volume = vol;
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
chan->leftvol = chan->rightvol = spacial->volume;
|
2022-06-05 07:57:13 +00:00
|
|
|
chan->loopstart = sfx->loopstart;
|
|
|
|
chan->buffer = buffer;
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-06-23 15:04:02 +00:00
|
|
|
s_spatialize (snd_t *snd, channel_t *ch)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
|
|
|
int phase; // in samples
|
|
|
|
vec_t dist, dot, lscale, rscale, scale;
|
|
|
|
vec3_t source_vec;
|
2022-06-04 03:10:09 +00:00
|
|
|
int chan_ind = ch - snd_channels;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
// prepare to lerp from prev to next phase
|
|
|
|
ch->oldphase = ch->phase;
|
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
spacial_t *spacial = &snd_spacialization[chan_ind];
|
|
|
|
|
2007-03-17 03:14:41 +00:00
|
|
|
// anything coming from the view entity will always be full volume
|
2010-08-11 23:46:28 +00:00
|
|
|
if (!snd_render_data.viewentity
|
2022-06-04 03:10:09 +00:00
|
|
|
|| snd_entity_channels[chan_ind].id == *snd_render_data.viewentity) {
|
2022-06-04 07:17:43 +00:00
|
|
|
ch->leftvol = spacial->volume;
|
|
|
|
ch->rightvol = spacial->volume;
|
2007-03-17 03:14:41 +00:00
|
|
|
ch->phase = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// calculate stereo seperation and distance attenuation
|
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
VectorSubtract (spacial->origin, listener_origin, source_vec);
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
dist = VectorNormalize (source_vec) * spacial->dist_mult;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
dot = DotProduct (listener_right, source_vec);
|
[cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.
As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.
The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).
While not used yet (partly due to working out the design), cvars can
have a validation function.
Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.
nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-23 03:22:45 +00:00
|
|
|
if (snd_swapchannelside)
|
2010-11-21 05:08:18 +00:00
|
|
|
dot = -dot;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2021-06-23 15:04:02 +00:00
|
|
|
if (snd->channels == 1) {
|
2007-03-17 03:14:41 +00:00
|
|
|
rscale = 1.0;
|
|
|
|
lscale = 1.0;
|
|
|
|
phase = 0;
|
|
|
|
} else {
|
[cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.
As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.
The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).
While not used yet (partly due to working out the design), cvars can
have a validation function.
Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.
nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-23 03:22:45 +00:00
|
|
|
rscale = 1.0 + dot * snd_volumesep;
|
|
|
|
lscale = 1.0 - dot * snd_volumesep;
|
|
|
|
phase = snd_phasesep * 0.001 * snd->speed * dot;
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add in distance effect
|
|
|
|
scale = (1.0 - dist) * rscale;
|
2022-06-04 07:17:43 +00:00
|
|
|
ch->rightvol = spacial->volume * scale;
|
2007-03-17 03:14:41 +00:00
|
|
|
if (ch->rightvol < 0)
|
|
|
|
ch->rightvol = 0;
|
|
|
|
|
|
|
|
scale = (1.0 - dist) * lscale;
|
2022-06-04 07:17:43 +00:00
|
|
|
ch->leftvol = spacial->volume * scale;
|
2007-03-17 03:14:41 +00:00
|
|
|
if (ch->leftvol < 0)
|
|
|
|
ch->leftvol = 0;
|
|
|
|
|
|
|
|
ch->phase = phase;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
2021-06-23 15:04:02 +00:00
|
|
|
s_update_channel (snd_t *snd, channel_t *ch)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2022-06-05 07:57:13 +00:00
|
|
|
if (!ch->buffer)
|
2007-03-17 03:14:41 +00:00
|
|
|
return 0;
|
2021-06-23 15:04:02 +00:00
|
|
|
s_spatialize (snd, ch);
|
2007-03-17 03:14:41 +00:00
|
|
|
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
|
2022-02-28 07:59:38 +00:00
|
|
|
SND_SetListener (snd_t *snd, transform_t *ear, const byte *ambient_sound_level)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2022-02-28 07:59:38 +00:00
|
|
|
if (ear) {
|
|
|
|
listener_origin = Transform_GetWorldPosition (ear);
|
|
|
|
listener_forward = Transform_Forward (ear);
|
|
|
|
listener_right = Transform_Right (ear);
|
|
|
|
listener_up = Transform_Up (ear);
|
|
|
|
} else {
|
2022-02-28 16:02:11 +00:00
|
|
|
listener_origin = (vec4f_t) {0, 0, 0, 1};
|
2022-02-28 07:59:38 +00:00
|
|
|
listener_forward = (vec4f_t) {1, 0, 0, 0};
|
|
|
|
listener_right = (vec4f_t) {0, -1, 0, 0};
|
|
|
|
listener_up = (vec4f_t) {0, 0, 1, 0};
|
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
// update general area ambient sound sources
|
2021-06-23 15:04:02 +00:00
|
|
|
s_updateAmbientSounds (snd, ambient_sound_level);
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
channel_t *combine = 0;
|
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
channel_t *ch = &snd_channels[i];
|
2022-06-05 07:57:13 +00:00
|
|
|
if (!ch->buffer || ch->done) {
|
2007-03-17 03:14:41 +00:00
|
|
|
continue;
|
|
|
|
}
|
2022-06-04 03:10:09 +00:00
|
|
|
if (set_is_member (&dynamic_channels, i)
|
|
|
|
|| set_is_member (&looped_channels, i)) {
|
|
|
|
// update spatialization for dynamic and looped sounds
|
|
|
|
s_update_channel (snd, ch);
|
|
|
|
} else if (set_is_member (&static_channels, i)) {
|
|
|
|
if (!s_update_channel (snd, ch)) {
|
|
|
|
// too quiet
|
|
|
|
continue;
|
|
|
|
}
|
2022-06-05 07:57:13 +00:00
|
|
|
//FIXME does this even work? probably better just to give
|
|
|
|
//static sounds random offsets (I suspect it worked just fine
|
|
|
|
//before streams were implemented)
|
2022-06-04 03:10:09 +00:00
|
|
|
// 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
|
2022-06-05 07:57:13 +00:00
|
|
|
if (combine && combine->buffer == ch->buffer) {
|
2007-03-17 03:14:41 +00:00
|
|
|
s_combine_channel (combine, ch);
|
2022-06-04 03:10:09 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// search for one
|
|
|
|
channel_t *c = 0;
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
|
|
if (set_is_member (&static_channels, j)) {
|
2022-06-05 07:57:13 +00:00
|
|
|
if (snd_channels[j].buffer == ch->buffer) {
|
2022-06-04 03:10:09 +00:00
|
|
|
c = &snd_channels[j];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((combine = c)) {
|
|
|
|
s_combine_channel (combine, ch);
|
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-03-25 08:12:43 +00:00
|
|
|
static int
|
2021-06-23 15:04:02 +00:00
|
|
|
snd_check_channels (snd_t *snd, channel_t *target_chan, const channel_t *check,
|
2022-06-05 07:57:13 +00:00
|
|
|
const sfx_t *sfx)
|
2007-03-25 08:12:43 +00:00
|
|
|
{
|
2022-06-05 07:57:13 +00:00
|
|
|
if (!check || !check->buffer || check == target_chan)
|
2007-03-25 08:12:43 +00:00
|
|
|
return 0;
|
2022-06-05 07:57:13 +00:00
|
|
|
if (*check->buffer->sfx == sfx && !check->pos) {
|
2021-06-23 15:04:02 +00:00
|
|
|
int skip = rand () % (int) (0.01 * snd->speed);
|
2007-03-25 08:12:43 +00:00
|
|
|
target_chan->pos = -skip;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-17 03:14:41 +00:00
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_StartSound (snd_t *snd, int entnum, int entchannel, sfx_t *sfx,
|
2022-06-03 07:54:57 +00:00
|
|
|
vec4f_t origin, float vol, float attenuation)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2021-06-23 15:04:02 +00:00
|
|
|
if (!sfx || !snd->speed)
|
2007-03-17 03:14:41 +00:00
|
|
|
return;
|
|
|
|
// pick a channel to play on
|
2022-06-05 07:57:13 +00:00
|
|
|
int looped = sfx->loopstart != (unsigned) -1;
|
|
|
|
channel_t *target_chan = s_pick_channel (snd, entnum, entchannel, looped);
|
2007-03-17 03:14:41 +00:00
|
|
|
if (!target_chan)
|
|
|
|
return;
|
2007-03-25 08:30:06 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
int chan_ind = target_chan - snd_channels;
|
2007-03-17 03:14:41 +00:00
|
|
|
// spatialize
|
2022-06-04 07:17:43 +00:00
|
|
|
spacial_t *spacial = &snd_spacialization[chan_ind];
|
|
|
|
VectorCopy (origin, spacial->origin);
|
|
|
|
spacial->dist_mult = attenuation / sound_nominal_clip_dist;
|
|
|
|
spacial->volume = vol;
|
2022-06-04 03:10:09 +00:00
|
|
|
snd_entity_channels[chan_ind] = (entchan_t) {
|
|
|
|
.id = entnum,
|
|
|
|
.channel = entchannel,
|
|
|
|
};
|
2021-06-23 15:04:02 +00:00
|
|
|
s_spatialize (snd, target_chan);
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-05 07:57:13 +00:00
|
|
|
sfxbuffer_t *buffer;
|
|
|
|
if (!(buffer = sfx->open (sfx))) {
|
2022-06-04 03:10:09 +00:00
|
|
|
// because the channel was never started, it's safe to directly free it
|
|
|
|
snd_free_channel (target_chan);
|
2007-03-17 03:14:41 +00:00
|
|
|
return;
|
2007-03-27 06:15:57 +00:00
|
|
|
}
|
2007-03-25 07:45:13 +00:00
|
|
|
target_chan->pos = 0;
|
|
|
|
target_chan->end = 0;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
|
|
|
// 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
|
2022-06-04 03:10:09 +00:00
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
if (set_is_member (&dynamic_channels, i)
|
|
|
|
|| set_is_member (&looped_channels, i)) {
|
|
|
|
channel_t *check = &snd_channels[i];
|
2022-06-05 07:57:13 +00:00
|
|
|
if (snd_check_channels (snd, target_chan, check, sfx))
|
2022-06-04 03:10:09 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-06-05 07:57:13 +00:00
|
|
|
target_chan->loopstart = sfx->loopstart;
|
|
|
|
target_chan->buffer = buffer;
|
2022-06-04 03:10:09 +00:00
|
|
|
set_add (looped ? &looped_channels : &dynamic_channels, chan_ind);
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
2007-03-21 12:56:43 +00:00
|
|
|
static int
|
2022-06-04 03:10:09 +00:00
|
|
|
s_check_stop (snd_t *snd, int chan_ind, int entnum, int entchannel)
|
2007-03-21 12:56:43 +00:00
|
|
|
{
|
2022-06-04 03:10:09 +00:00
|
|
|
entchan_t *entchan = &snd_entity_channels[chan_ind];
|
|
|
|
if (entchan->id == entnum && entchan->channel == entchannel) {
|
|
|
|
SND_ChannelStop (snd, &snd_channels[chan_ind]);
|
2007-03-21 12:56:43 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-17 03:14:41 +00:00
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_StopSound (snd_t *snd, int entnum, int entchannel)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
2022-06-04 03:10:09 +00:00
|
|
|
for (int i = 0; i < MAX_CHANNELS; i++) {
|
|
|
|
if (set_is_member (&dynamic_channels, i)
|
|
|
|
|| set_is_member (&looped_channels, i)) {
|
|
|
|
s_check_stop (snd, i, entnum, entchannel);
|
|
|
|
}
|
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-03-30 14:38:01 +00:00
|
|
|
SND_StaticSound (snd_t *snd, sfx_t *sfx, vec4f_t origin, float vol,
|
2007-03-17 03:14:41 +00:00
|
|
|
float attenuation)
|
|
|
|
{
|
|
|
|
if (!sfx)
|
|
|
|
return;
|
2007-03-27 06:15:57 +00:00
|
|
|
if (sfx->loopstart == (unsigned int) -1) {
|
|
|
|
Sys_Printf ("Sound %s not looped\n", sfx->name);
|
|
|
|
return;
|
|
|
|
}
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-05 07:57:13 +00:00
|
|
|
channel_t *ss;
|
2022-06-04 03:10:09 +00:00
|
|
|
if (!(ss = SND_AllocChannel (snd))) {
|
|
|
|
Sys_Printf ("ran out of channels\n");
|
|
|
|
return;
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
2022-06-04 03:10:09 +00:00
|
|
|
int ss_ind = ss - snd_channels;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2022-06-05 07:57:13 +00:00
|
|
|
sfxbuffer_t *buffer;
|
|
|
|
if (!(buffer = sfx->open (sfx)))
|
2007-03-17 03:14:41 +00:00
|
|
|
return;
|
2007-03-27 04:12:04 +00:00
|
|
|
|
2022-06-04 07:17:43 +00:00
|
|
|
spacial_t *spacial = &snd_spacialization[ss_ind];
|
|
|
|
VectorCopy (origin, spacial->origin);
|
|
|
|
spacial->volume = vol;
|
|
|
|
spacial->dist_mult = attenuation / sound_nominal_clip_dist;
|
2007-05-06 08:35:28 +00:00
|
|
|
ss->end = 0;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2021-06-23 15:04:02 +00:00
|
|
|
s_spatialize (snd, ss);
|
2007-03-17 03:14:41 +00:00
|
|
|
ss->oldphase = ss->phase;
|
2007-03-18 10:32:01 +00:00
|
|
|
|
2022-06-04 03:10:09 +00:00
|
|
|
set_add (&static_channels, ss_ind);
|
|
|
|
snd_entity_channels[ss_ind] = (entchan_t) {
|
|
|
|
.id = SND_STATIC_ID,
|
|
|
|
.channel = 0,
|
|
|
|
};
|
2022-06-05 07:57:13 +00:00
|
|
|
ss->loopstart = sfx->loopstart;
|
|
|
|
ss->buffer = buffer;
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2021-06-23 15:04:02 +00:00
|
|
|
SND_LocalSound (snd_t *snd, const char *sound)
|
2007-03-17 03:14:41 +00:00
|
|
|
{
|
|
|
|
sfx_t *sfx;
|
2012-12-17 06:07:49 +00:00
|
|
|
int viewent = 0;
|
2007-03-17 03:14:41 +00:00
|
|
|
|
2021-06-23 15:04:02 +00:00
|
|
|
sfx = SND_PrecacheSound (snd, sound);
|
2007-03-17 03:14:41 +00:00
|
|
|
if (!sfx) {
|
2022-06-06 03:05:44 +00:00
|
|
|
Sys_Printf ("S_LocalSound: can't load %s\n", sound);
|
2007-03-17 03:14:41 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-12-17 06:07:49 +00:00
|
|
|
if (snd_render_data.viewentity)
|
|
|
|
viewent = *snd_render_data.viewentity;
|
2022-03-30 14:38:01 +00:00
|
|
|
SND_StartSound (snd, viewent, -1, sfx, (vec4f_t) {0, 0, 0, 1}, 1, 1);
|
2007-03-17 03:14:41 +00:00
|
|
|
}
|
2022-06-04 07:17:43 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
SND_ChannelSetVolume (channel_t *chan, float volume)
|
|
|
|
{
|
|
|
|
int chan_ind = chan - snd_channels;
|
|
|
|
snd_spacialization[chan_ind].volume = volume;
|
|
|
|
chan->leftvol = chan->rightvol = volume;
|
|
|
|
}
|