X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=snd_main.c;h=2c32953ab92a991b5006a3672727108605b17aa1;hb=81ab203d90dc99227801821479e837f772b00369;hp=a3609065508cfb94acea367c917e12d8012ece21;hpb=f3df94567a65ebde740efe0c78e83610019196f9;p=xonotic%2Fdarkplaces.git diff --git a/snd_main.c b/snd_main.c index a3609065..2c32953a 100644 --- a/snd_main.c +++ b/snd_main.c @@ -25,13 +25,110 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "snd_ogg.h" -void S_Play(void); -void S_PlayVol(void); -void S_Play2(void); -void S_SoundList(void); -void S_Update_(); +#define SND_MIN_SPEED 8000 +#define SND_MAX_SPEED 48000 +#define SND_MIN_WIDTH 1 +#define SND_MAX_WIDTH 2 +#define SND_MIN_CHANNELS 1 +#define SND_MAX_CHANNELS 8 + +#if SND_LISTENERS != 8 +# error this data only supports up to 8 channel, update it! +#endif +typedef struct listener_s +{ + float yawangle; + float dotscale; + float dotbias; + float ambientvolume; +} +listener_t; +typedef struct speakerlayout_s +{ + const char *name; + unsigned int channels; + listener_t listeners[SND_LISTENERS]; +} +speakerlayout_t; -void S_ClearBuffer (void); +static speakerlayout_t snd_speakerlayout; + +// Our speaker layouts are based on ALSA. They differ from those +// Win32 and Mac OS X APIs use when there's more than 4 channels. +// (rear left + rear right, and front center + LFE are swapped). +#define SND_SPEAKERLAYOUTS (sizeof(snd_speakerlayouts) / sizeof(snd_speakerlayouts[0])) +static const speakerlayout_t snd_speakerlayouts[] = +{ + { + "surround71", 8, + { + {45, 0.2, 0.2, 0.5}, // front left + {315, 0.2, 0.2, 0.5}, // front right + {135, 0.2, 0.2, 0.5}, // rear left + {225, 0.2, 0.2, 0.5}, // rear right + {0, 0.2, 0.2, 0.5}, // front center + {0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) + {90, 0.2, 0.2, 0.5}, // side left + {180, 0.2, 0.2, 0.5}, // side right + } + }, + { + "surround51", 6, + { + {45, 0.2, 0.2, 0.5}, // front left + {315, 0.2, 0.2, 0.5}, // front right + {135, 0.2, 0.2, 0.5}, // rear left + {225, 0.2, 0.2, 0.5}, // rear right + {0, 0.2, 0.2, 0.5}, // front center + {0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) + {0, 0, 0, 0}, + {0, 0, 0, 0}, + } + }, + { + // these systems sometimes have a subwoofer as well, but it has no + // channel of its own + "surround40", 4, + { + {45, 0.3, 0.3, 0.8}, // front left + {315, 0.3, 0.3, 0.8}, // front right + {135, 0.3, 0.3, 0.8}, // rear left + {225, 0.3, 0.3, 0.8}, // rear right + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + } + }, + { + // these systems sometimes have a subwoofer as well, but it has no + // channel of its own + "stereo", 2, + { + {90, 0.5, 0.5, 1}, // side left + {270, 0.5, 0.5, 1}, // side right + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + } + }, + { + "mono", 1, + { + {0, 0, 1, 1}, // center + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + } + } +}; // ======================================================================= @@ -41,119 +138,549 @@ void S_ClearBuffer (void); channel_t channels[MAX_CHANNELS]; unsigned int total_channels; -int snd_blocked = 0; -cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0"}; -cvar_t snd_streaming = { CVAR_SAVE, "snd_streaming", "1"}; - -volatile dma_t *shm = 0; -volatile dma_t sn; +snd_ringbuffer_t *snd_renderbuffer = NULL; +unsigned int soundtime = 0; +static unsigned int oldpaintedtime = 0; +static unsigned int extrasoundtime = 0; +static double snd_starttime = 0.0; vec3_t listener_origin; -matrix4x4_t listener_matrix; -vec_t sound_nominal_clip_dist=1000.0; +matrix4x4_t listener_matrix[SND_LISTENERS]; mempool_t *snd_mempool; -// sample PAIRS -int soundtime; -int paintedtime; - - // Linked list of known sfx -sfx_t *known_sfx = NULL; +static sfx_t *known_sfx = NULL; -qboolean sound_started = false; -qboolean sound_spatialized = false; +static qboolean sound_spatialized = false; -// Fake dma is a synchronous faking of the DMA progress used for -// isolating performance in the renderer. -qboolean fakedma = false; +qboolean simsound = false; -cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1"}; -cvar_t volume = {CVAR_SAVE, "volume", "0.7"}; -cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1"}; +static qboolean recording_sound = false; -cvar_t nosound = {0, "nosound", "0"}; -cvar_t snd_precache = {0, "snd_precache", "1"}; -cvar_t bgmbuffer = {0, "bgmbuffer", "4096"}; -cvar_t ambient_level = {0, "ambient_level", "0.3"}; -cvar_t ambient_fade = {0, "ambient_fade", "100"}; -cvar_t snd_noextraupdate = {0, "snd_noextraupdate", "0"}; -cvar_t snd_show = {0, "snd_show", "0"}; -cvar_t _snd_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.1"}; -cvar_t snd_swapstereo = {CVAR_SAVE, "snd_swapstereo", "0"}; +int snd_blocked = 0; +static int current_swapstereo = false; +static int current_channellayout = SND_CHANNELLAYOUT_AUTO; +static int current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + +// Cvars declared in sound.h (part of the sound API) +cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; +cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; +cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; +cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; +cvar_t snd_soundradius = {0, "snd_soundradius", "1000", "radius of weapon sounds and other standard sound effects (monster idle noises are half this radius and flickering light noises are one third of this radius)"}; + +// Cvars declared in snd_main.h (shared with other snd_*.c files) +cvar_t _snd_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.1", "how much sound to mix ahead of time"}; +cvar_t snd_streaming = { CVAR_SAVE, "snd_streaming", "1", "enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory)"}; +cvar_t snd_swapstereo = {CVAR_SAVE, "snd_swapstereo", "0", "swaps left/right speakers for old ISA soundblaster cards"}; +cvar_t snd_channellayout = {0, "snd_channellayout", "0", "channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout)"}; + +// Local cvars +static cvar_t nosound = {0, "nosound", "0", "disables sound"}; +static cvar_t snd_precache = {0, "snd_precache", "1", "loads sounds before they are used"}; +static cvar_t ambient_level = {0, "ambient_level", "0.3", "volume of environment noises (water and wind)"}; +static cvar_t ambient_fade = {0, "ambient_fade", "100", "rate of volume fading when moving from one environment to another"}; +static cvar_t snd_noextraupdate = {0, "snd_noextraupdate", "0", "disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates"}; +static cvar_t snd_show = {0, "snd_show", "0", "shows some statistics about sound mixing"}; + +// Default sound format is 48KHz, 16-bit, stereo +// (48KHz because a lot of onboard sound cards sucks at any other speed) +static cvar_t snd_speed = {CVAR_SAVE, "snd_speed", "48000", "sound output frequency, in hertz"}; +static cvar_t snd_width = {CVAR_SAVE, "snd_width", "2", "sound output precision, in bytes (1 and 2 supported)"}; +static cvar_t snd_channels = {CVAR_SAVE, "snd_channels", "2", "number of channels for the sound ouput (2 for stereo; up to 8 supported for 3D sound)"}; + +// Ambient sounds +static sfx_t* ambient_sfxs [2] = { NULL, NULL }; +static const char* ambient_names [2] = { "sound/ambience/water1.wav", "sound/ambience/wind2.wav" }; // ==================================================================== -// User-setable variables +// Functions // ==================================================================== +void S_FreeSfx (sfx_t *sfx, qboolean force); + +static void S_Play_Common (float fvol, float attenuation) +{ + int i, ch_ind; + char name [MAX_QPATH]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc ()) + { + // Get the name, and appends ".wav" as an extension if there's none + strlcpy (name, Cmd_Argv (i), sizeof (name)); + if (!strrchr (name, '.')) + strlcat (name, ".wav", sizeof (name)); + i++; + + // If we need to get the volume from the command line + if (fvol == -1.0f) + { + fvol = atof (Cmd_Argv (i)); + i++; + } + + sfx = S_PrecacheSound (name, true, false); + if (sfx) + { + ch_ind = S_StartSound (-1, 0, sfx, listener_origin, fvol, attenuation); + + // Free the sfx if the file didn't exist + if (ch_ind < 0) + S_FreeSfx (sfx, false); + else + channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; + } + } +} + +static void S_Play_f(void) +{ + S_Play_Common (1.0f, 1.0f); +} + +static void S_Play2_f(void) +{ + S_Play_Common (1.0f, 0.0f); +} + +static void S_PlayVol_f(void) +{ + S_Play_Common (-1.0f, 0.0f); +} + +static void S_SoundList_f (void) +{ + unsigned int i; + sfx_t *sfx; + unsigned int total; + + total = 0; + for (sfx = known_sfx, i = 0; sfx != NULL; sfx = sfx->next, i++) + { + if (sfx->fetcher != NULL) + { + unsigned int size; + const snd_format_t* format; + + size = sfx->memsize; + format = sfx->fetcher->getfmt(sfx); + Con_Printf ("%c%c%c%c(%2db, %6s) %8i : %s\n", + (sfx->loopstart >= 0) ? 'L' : ' ', + (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', + (sfx->locks > 0) ? 'K' : ' ', + (sfx->flags & SFXFLAG_PERMANENTLOCK) ? 'P' : ' ', + format->width * 8, + (format->channels == 1) ? "mono" : "stereo", + size, + sfx->name); + total += size; + } + else + Con_Printf (" ( unknown ) unloaded : %s\n", sfx->name); + } + Con_Printf("Total resident: %i\n", total); +} + + void S_SoundInfo_f(void) { - if (!sound_started || !shm) + if (snd_renderbuffer == NULL) { Con_Print("sound system not started\n"); return; } - Con_Printf("%5d stereo\n", shm->format.channels - 1); - Con_Printf("%5d samples\n", shm->samples); - Con_Printf("%5d samplepos\n", shm->samplepos); - Con_Printf("%5d samplebits\n", shm->format.width * 8); - Con_Printf("%5d speed\n", shm->format.speed); - Con_Printf("%p dma buffer\n", shm->buffer); + Con_Printf("%5d speakers\n", snd_renderbuffer->format.channels); + Con_Printf("%5d frames\n", snd_renderbuffer->maxframes); + Con_Printf("%5d samplebits\n", snd_renderbuffer->format.width * 8); + Con_Printf("%5d speed\n", snd_renderbuffer->format.speed); Con_Printf("%5u total_channels\n", total_channels); } -void S_Startup(void) +int S_GetSoundRate(void) +{ + return snd_renderbuffer ? snd_renderbuffer->format.speed : 0; +} + + +static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels) +{ + static const snd_format_t thresholds [] = + { + // speed width channels + { SND_MIN_SPEED, SND_MIN_WIDTH, SND_MIN_CHANNELS }, + { 11025, 1, 2 }, + { 22050, 2, 2 }, + { 44100, 2, 2 }, + { 48000, 2, 6 }, + { SND_MAX_SPEED, SND_MAX_WIDTH, SND_MAX_CHANNELS }, + }; + const unsigned int nb_thresholds = sizeof(thresholds) / sizeof(thresholds[0]); + unsigned int speed_level, width_level, channels_level; + + // If we have reached the minimum values, there's nothing more we can do + if ((format->speed == thresholds[0].speed || fixed_speed) && + (format->width == thresholds[0].width || fixed_width) && + (format->channels == thresholds[0].channels || fixed_channels)) + return false; + + // Check the min and max values + #define CHECK_BOUNDARIES(param) \ + if (format->param < thresholds[0].param) \ + { \ + format->param = thresholds[0].param; \ + return true; \ + } \ + if (format->param > thresholds[nb_thresholds - 1].param) \ + { \ + format->param = thresholds[nb_thresholds - 1].param; \ + return true; \ + } + CHECK_BOUNDARIES(speed); + CHECK_BOUNDARIES(width); + CHECK_BOUNDARIES(channels); + #undef CHECK_BOUNDARIES + + // Find the level of each parameter + #define FIND_LEVEL(param) \ + param##_level = 0; \ + while (param##_level < nb_thresholds - 1) \ + { \ + if (format->param <= thresholds[param##_level].param) \ + break; \ + \ + param##_level++; \ + } + FIND_LEVEL(speed); + FIND_LEVEL(width); + FIND_LEVEL(channels); + #undef FIND_LEVEL + + // Decrease the parameter with the highest level to the previous level + if (channels_level >= speed_level && channels_level >= width_level && !fixed_channels) + { + format->channels = thresholds[channels_level - 1].channels; + return true; + } + if (speed_level >= width_level && !fixed_speed) + { + format->speed = thresholds[speed_level - 1].speed; + return true; + } + + format->width = thresholds[width_level - 1].width; + return true; +} + + +#define SWAP_LISTENERS(l1, l2, tmpl) { tmpl = (l1); (l1) = (l2); (l2) = tmpl; } + +static void S_SetChannelLayout (void) +{ + unsigned int i; + listener_t swaplistener; + listener_t *listeners; + int layout; + + for (i = 0; i < SND_SPEAKERLAYOUTS; i++) + if (snd_speakerlayouts[i].channels == snd_renderbuffer->format.channels) + break; + if (i >= SND_SPEAKERLAYOUTS) + { + Con_Printf("S_SetChannelLayout: can't find the speaker layout for %hu channels. Defaulting to mono output\n", + snd_renderbuffer->format.channels); + i = SND_SPEAKERLAYOUTS - 1; + } + + snd_speakerlayout = snd_speakerlayouts[i]; + listeners = snd_speakerlayout.listeners; + + // Swap the left and right channels if snd_swapstereo is set + if (snd_swapstereo.integer) + { + switch (snd_speakerlayout.channels) + { + case 8: + SWAP_LISTENERS(listeners[6], listeners[7], swaplistener); + // no break + case 4: + case 6: + SWAP_LISTENERS(listeners[2], listeners[3], swaplistener); + // no break + case 2: + SWAP_LISTENERS(listeners[0], listeners[1], swaplistener); + break; + + default: + case 1: + // Nothing to do + break; + } + } + + // Sanity check + if (snd_channellayout.integer < SND_CHANNELLAYOUT_AUTO || + snd_channellayout.integer > SND_CHANNELLAYOUT_ALSA) + Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); + + if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) + { + // If we're in the sound engine initialization + if (current_channellayout_used == SND_CHANNELLAYOUT_AUTO) + { + layout = SND_CHANNELLAYOUT_STANDARD; + Cvar_SetValueQuick (&snd_channellayout, layout); + } + else + layout = current_channellayout_used; + } + else + layout = snd_channellayout.integer; + + // Convert our layout (= ALSA) to the standard layout if necessary + if (snd_speakerlayout.channels == 6 || snd_speakerlayout.channels == 8) + { + if (layout == SND_CHANNELLAYOUT_STANDARD) + { + SWAP_LISTENERS(listeners[2], listeners[4], swaplistener); + SWAP_LISTENERS(listeners[3], listeners[5], swaplistener); + } + + Con_Printf("S_SetChannelLayout: using %s speaker layout for 3D sound\n", + (layout == SND_CHANNELLAYOUT_ALSA) ? "ALSA" : "standard"); + } + + current_swapstereo = snd_swapstereo.integer; + current_channellayout = snd_channellayout.integer; + current_channellayout_used = layout; +} + + +void S_Startup (void) { + qboolean fixed_speed, fixed_width, fixed_channels; + snd_format_t chosen_fmt; + static snd_format_t prev_render_format = {0, 0, 0}; + const char* env; + int i; + if (!snd_initialized.integer) return; - shm = &sn; - memset((void *)shm, 0, sizeof(*shm)); + fixed_speed = false; + fixed_width = false; + fixed_channels = false; + + // Get the starting sound format from the cvars + chosen_fmt.speed = snd_speed.integer; + chosen_fmt.width = snd_width.integer; + chosen_fmt.channels = snd_channels.integer; - // create a piece of DMA memory - if (fakedma) + // Check the environment variables to see if the player wants a particular sound format + env = getenv("QUAKE_SOUND_CHANNELS"); + if (env != NULL) { - shm->format.width = 2; - shm->format.speed = 22050; - shm->format.channels = 2; - shm->samples = 32768; - shm->samplepos = 0; - shm->buffer = Mem_Alloc(snd_mempool, shm->format.channels * shm->samples * shm->format.width); + chosen_fmt.channels = atoi (env); + fixed_channels = true; } - else + env = getenv("QUAKE_SOUND_SPEED"); + if (env != NULL) + { + chosen_fmt.speed = atoi (env); + fixed_speed = true; + } + env = getenv("QUAKE_SOUND_SAMPLEBITS"); + if (env != NULL) + { + chosen_fmt.width = atoi (env) / 8; + fixed_width = true; + } + + // Parse the command line to see if the player wants a particular sound format +// COMMANDLINEOPTION: Sound: -sndquad sets sound output to 4 channel surround + if (COM_CheckParm ("-sndquad") != 0) { - if (!SNDDMA_Init()) + chosen_fmt.channels = 4; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndstereo sets sound output to stereo + else if (COM_CheckParm ("-sndstereo") != 0) + { + chosen_fmt.channels = 2; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndmono sets sound output to mono + else if (COM_CheckParm ("-sndmono") != 0) + { + chosen_fmt.channels = 1; + fixed_channels = true; + } +// COMMANDLINEOPTION: Sound: -sndspeed chooses sound output rate (supported values are 48000, 44100, 32000, 24000, 22050, 16000, 11025 (quake), 8000) + i = COM_CheckParm ("-sndspeed"); + if (0 < i && i < com_argc - 1) + { + chosen_fmt.speed = atoi (com_argv[i + 1]); + fixed_speed = true; + } +// COMMANDLINEOPTION: Sound: -sndbits chooses 8 bit or 16 bit sound output + i = COM_CheckParm ("-sndbits"); + if (0 < i && i < com_argc - 1) + { + chosen_fmt.width = atoi (com_argv[i + 1]) / 8; + fixed_width = true; + } + + // You can't change sound speed after start time (not yet supported) + if (prev_render_format.speed != 0) + { + fixed_speed = true; + if (chosen_fmt.speed != prev_render_format.speed) + { + Con_Printf("S_Startup: sound speed has changed! This is NOT supported yet. Falling back to previous speed (%u Hz)\n", + prev_render_format.speed); + chosen_fmt.speed = prev_render_format.speed; + } + } + + // Sanity checks + if (chosen_fmt.speed < SND_MIN_SPEED) + { + chosen_fmt.speed = SND_MIN_SPEED; + fixed_speed = false; + } + else if (chosen_fmt.speed > SND_MAX_SPEED) + { + chosen_fmt.speed = SND_MAX_SPEED; + fixed_speed = false; + } + + if (chosen_fmt.width < SND_MIN_WIDTH) + { + chosen_fmt.width = SND_MIN_WIDTH; + fixed_width = false; + } + else if (chosen_fmt.width > SND_MAX_WIDTH) + { + chosen_fmt.width = SND_MAX_WIDTH; + fixed_width = false; + } + + if (chosen_fmt.channels < SND_MIN_CHANNELS) + { + chosen_fmt.channels = SND_MIN_CHANNELS; + fixed_channels = false; + } + else if (chosen_fmt.channels > SND_MAX_CHANNELS) + { + chosen_fmt.channels = SND_MAX_CHANNELS; + fixed_channels = false; + } + + // create the sound buffer used for sumitting the samples to the plaform-dependent module + if (!simsound) + { + snd_format_t suggest_fmt; + qboolean accepted; + + accepted = false; + do { - Con_Print("S_Startup: SNDDMA_Init failed.\n"); - shm = NULL; - sound_started = false; + Con_DPrintf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n", + chosen_fmt.speed, chosen_fmt.width * 8, + chosen_fmt.channels); + + memset(&suggest_fmt, 0, sizeof(suggest_fmt)); + accepted = SndSys_Init(&chosen_fmt, &suggest_fmt); + + if (!accepted) + { + Con_DPrintf("S_Startup: sound output initialization FAILED\n"); + + // If the module is suggesting another one + if (suggest_fmt.speed != 0) + { + memcpy(&chosen_fmt, &suggest_fmt, sizeof(chosen_fmt)); + Con_Printf (" Driver has suggested %dHz, %d bit, %d channels. Retrying...\n", + suggest_fmt.speed, suggest_fmt.width * 8, + suggest_fmt.channels); + } + // Else, try to find a less resource-demanding format + else if (!S_ChooseCheaperFormat (&chosen_fmt, fixed_speed, fixed_width, fixed_channels)) + break; + } + } while (!accepted); + + // If we haven't found a suitable format + if (!accepted) + { + Con_Print("S_Startup: SndSys_Init failed.\n"); sound_spatialized = false; return; } } + else + { + snd_renderbuffer = Snd_CreateRingBuffer(&chosen_fmt, 0, NULL); + Con_Print ("S_Startup: simulating sound output\n"); + } - sound_started = true; + memcpy(&prev_render_format, &snd_renderbuffer->format, sizeof(prev_render_format)); + Con_Printf("Sound format: %dHz, %d channels, %d bits per sample\n", + chosen_fmt.speed, chosen_fmt.channels, chosen_fmt.width * 8); - Con_DPrintf("Sound sampling rate: %i\n", shm->format.speed); + // Update the cvars + if (snd_speed.integer != (int)chosen_fmt.speed) + Cvar_SetValueQuick(&snd_speed, chosen_fmt.speed); + if (snd_width.integer != chosen_fmt.width) + Cvar_SetValueQuick(&snd_width, chosen_fmt.width); + if (snd_channels.integer != chosen_fmt.channels) + Cvar_SetValueQuick(&snd_channels, chosen_fmt.channels); - S_StopAllSounds (); + current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + S_SetChannelLayout(); + + snd_starttime = realtime; + + // If the sound module has already run, add an extra time to make sure + // the sound time doesn't decrease, to not confuse playing SFXs + if (oldpaintedtime != 0) + { + // The extra time must be a multiple of the render buffer size + // to avoid modifying the current position in the buffer, + // some modules write directly to a shared (DMA) buffer + extrasoundtime = oldpaintedtime + snd_renderbuffer->maxframes - 1; + extrasoundtime -= extrasoundtime % snd_renderbuffer->maxframes; + Con_DPrintf("S_Startup: extra sound time = %u\n", extrasoundtime); + + soundtime = extrasoundtime; + } + else + extrasoundtime = 0; + snd_renderbuffer->startframe = soundtime; + snd_renderbuffer->endframe = soundtime; + recording_sound = false; } void S_Shutdown(void) { - if (!sound_started) + if (snd_renderbuffer == NULL) return; - if (fakedma) - Mem_Free(shm->buffer); + oldpaintedtime = snd_renderbuffer->endframe; + + if (simsound) + { + Mem_Free(snd_renderbuffer->ring); + Mem_Free(snd_renderbuffer); + snd_renderbuffer = NULL; + } else - SNDDMA_Shutdown(); + SndSys_Shutdown(); - shm = NULL; - sound_started = false; sound_spatialized = false; } @@ -176,6 +703,10 @@ void S_Init(void) Cvar_RegisterVariable(&bgmvolume); Cvar_RegisterVariable(&snd_staticvolume); + Cvar_RegisterVariable(&snd_speed); + Cvar_RegisterVariable(&snd_width); + Cvar_RegisterVariable(&snd_channels); + // COMMANDLINEOPTION: Sound: -nosound disables sound (including CD audio) if (COM_CheckParm("-nosound") || COM_CheckParm("-safe")) return; @@ -184,27 +715,29 @@ void S_Init(void) // COMMANDLINEOPTION: Sound: -simsound runs sound mixing but with no output if (COM_CheckParm("-simsound")) - fakedma = true; + simsound = true; - Cmd_AddCommand("play", S_Play); - Cmd_AddCommand("play2", S_Play2); - Cmd_AddCommand("playvol", S_PlayVol); - Cmd_AddCommand("stopsound", S_StopAllSounds); - Cmd_AddCommand("soundlist", S_SoundList); - Cmd_AddCommand("soundinfo", S_SoundInfo_f); - Cmd_AddCommand("snd_restart", S_Restart_f); + Cmd_AddCommand("play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); + Cmd_AddCommand("play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); + Cmd_AddCommand("playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); + Cmd_AddCommand("stopsound", S_StopAllSounds, "silence"); + Cmd_AddCommand("soundlist", S_SoundList_f, "list loaded sounds"); + Cmd_AddCommand("soundinfo", S_SoundInfo_f, "print sound system information (such as channels and speed)"); + Cmd_AddCommand("snd_restart", S_Restart_f, "restart sound system"); + Cmd_AddCommand("snd_reload", S_Reload_f, "reload all sound files"); Cvar_RegisterVariable(&nosound); Cvar_RegisterVariable(&snd_precache); Cvar_RegisterVariable(&snd_initialized); Cvar_RegisterVariable(&snd_streaming); - Cvar_RegisterVariable(&bgmbuffer); Cvar_RegisterVariable(&ambient_level); Cvar_RegisterVariable(&ambient_fade); Cvar_RegisterVariable(&snd_noextraupdate); Cvar_RegisterVariable(&snd_show); Cvar_RegisterVariable(&_snd_mixahead); Cvar_RegisterVariable(&snd_swapstereo); // for people with backwards sound wiring + Cvar_RegisterVariable(&snd_channellayout); + Cvar_RegisterVariable(&snd_soundradius); Cvar_SetValueQuick(&snd_initialized, true); @@ -217,39 +750,78 @@ void S_Init(void) } -// ======================================================================= -// Load a sound -// ======================================================================= +/* +================ +S_Terminate + +Shutdown and free all resources +================ +*/ +void S_Terminate (void) +{ + S_Shutdown (); + OGG_CloseLibrary (); + + // Free all SFXs + while (known_sfx != NULL) + S_FreeSfx (known_sfx, true); + + Cvar_SetValueQuick (&snd_initialized, false); + Mem_FreePool (&snd_mempool); +} + /* ================== -S_FindName +S_Reload_f +================== +*/ +void S_Reload_f (void) +{ + int i; + + // stop any active sounds + S_StopAllSounds(); + + // because the ambient sounds will be freed, clear the pointers + for (i = 0;i < (int)sizeof (ambient_sfxs) / (int)sizeof (ambient_sfxs[0]);i++) + ambient_sfxs[i] = NULL; + + // now free all sounds + while (known_sfx != NULL) + S_FreeSfx (known_sfx, true); +} + +/* +================== +S_FindName ================== */ -sfx_t *S_FindName (const char *name, qboolean stdpath) +sfx_t *S_FindName (const char *name) { sfx_t *sfx; - size_t len; - char namebuffer [MAX_QPATH]; if (!snd_initialized.integer) return NULL; - // Add the default sound directory to the path - len = snprintf (namebuffer, sizeof (namebuffer), stdpath ? "sound/%s" : "%s", name); - if (len >= sizeof (namebuffer)) - Host_Error ("S_FindName: sound name too long (%s)", name); + if (strlen (name) >= sizeof (sfx->name)) + { + Con_Printf ("S_FindName: sound name too long (%s)\n", name); + return NULL; + } // Look for this sound in the list of known sfx + // TODO: hash table search? for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) - if(!strcmp (sfx->name, namebuffer)) + if(!strcmp (sfx->name, name)) return sfx; // Add a sfx_t struct for this sound - sfx = Mem_Alloc (snd_mempool, sizeof (*sfx)); + sfx = (sfx_t *)Mem_Alloc (snd_mempool, sizeof (*sfx)); memset (sfx, 0, sizeof(*sfx)); - strlcpy (sfx->name, namebuffer, sizeof (sfx->name)); + strlcpy (sfx->name, name, sizeof (sfx->name)); + sfx->memsize = sizeof(*sfx); sfx->next = known_sfx; known_sfx = sfx; @@ -260,13 +832,14 @@ sfx_t *S_FindName (const char *name, qboolean stdpath) /* ================== S_FreeSfx - ================== */ -void S_FreeSfx (sfx_t *sfx) +void S_FreeSfx (sfx_t *sfx, qboolean force) { - // Never free a locked sfx - if (sfx->locks > 0) + unsigned int i; + + // Never free a locked sfx unless forced + if (!force && (sfx->locks > 0 || (sfx->flags & SFXFLAG_PERMANENTLOCK))) return; Con_DPrintf ("S_FreeSfx: freeing %s\n", sfx->name); @@ -285,11 +858,20 @@ void S_FreeSfx (sfx_t *sfx) break; } if (prev_sfx == NULL) - Sys_Error ("S_FreeSfx: Can't find SFX %s in the list!\n", sfx->name); + { + Con_Printf ("S_FreeSfx: Can't find SFX %s in the list!\n", sfx->name); + return; + } } + // Stop all channels using this sfx + for (i = 0; i < total_channels; i++) + if (channels[i].sfx == sfx) + S_StopChannel (i); + // Free it - Mem_FreePool (&sfx->mempool); + if (sfx->fetcher != NULL && sfx->fetcher->free != NULL) + sfx->fetcher->free (sfx); Mem_Free (sfx); } @@ -297,50 +879,59 @@ void S_FreeSfx (sfx_t *sfx) /* ================== S_ServerSounds - ================== */ void S_ServerSounds (char serversound [][MAX_QPATH], unsigned int numsounds) { sfx_t *sfx; + sfx_t *sfxnext; unsigned int i; // Start the ambient sounds and make them loop - channels[AMBIENT_WATER].sfx = S_PrecacheSound ("ambience/water1.wav", false, true, true); - channels[AMBIENT_SKY].sfx = S_PrecacheSound ("ambience/wind2.wav", false, true, true); - for (i = 0; i < NUM_AMBIENTS; i++) - channels[i].flags |= CHANNELFLAG_FORCELOOP; + for (i = 0; i < sizeof (ambient_sfxs) / sizeof (ambient_sfxs[0]); i++) + { + // Precache it if it's not done (request a lock to make sure it will never be freed) + if (ambient_sfxs[i] == NULL) + ambient_sfxs[i] = S_PrecacheSound (ambient_names[i], false, true); + if (ambient_sfxs[i] != NULL) + { + // Add a lock to the SFX while playing. It will be + // removed by S_StopAllSounds at the end of the level + S_LockSfx (ambient_sfxs[i]); + + channels[i].sfx = ambient_sfxs[i]; + channels[i].flags |= CHANNELFLAG_FORCELOOP; + channels[i].master_vol = 0; + } + } // Remove 1 lock from all sfx with the SFXFLAG_SERVERSOUND flag, and remove the flag for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) if (sfx->flags & SFXFLAG_SERVERSOUND) { - sfx->locks--; + S_UnlockSfx (sfx); sfx->flags &= ~SFXFLAG_SERVERSOUND; } // Add 1 lock and the SFXFLAG_SERVERSOUND flag to each sfx in "serversound" for (i = 1; i < numsounds; i++) { - sfx = S_FindName (serversound[i], true); + sfx = S_FindName (serversound[i]); if (sfx != NULL) { - sfx->locks++; + // clear the FILEMISSING flag so that S_LoadSound will try again on a + // previously missing file + sfx->flags &= ~ SFXFLAG_FILEMISSING; + S_LockSfx (sfx); sfx->flags |= SFXFLAG_SERVERSOUND; } } // Free all unlocked sfx - sfx = known_sfx; - while (sfx != NULL) + for (sfx = known_sfx;sfx;sfx = sfxnext) { - sfx_t* crtsfx; - - // We may lose the "next" pointer after S_FreeSfx - crtsfx = sfx; - sfx = sfx->next; - - S_FreeSfx (crtsfx); + sfxnext = sfx->next; + S_FreeSfx (sfx, false); } } @@ -348,22 +939,29 @@ void S_ServerSounds (char serversound [][MAX_QPATH], unsigned int numsounds) /* ================== S_PrecacheSound - ================== */ -sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean stdpath, qboolean lock) +sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean lock) { sfx_t *sfx; if (!snd_initialized.integer) return NULL; - sfx = S_FindName (name, stdpath); + if (name == NULL || name[0] == 0) + return NULL; + + sfx = S_FindName (name); + if (sfx == NULL) return NULL; + // clear the FILEMISSING flag so that S_LoadSound will try again on a + // previously missing file + sfx->flags &= ~ SFXFLAG_FILEMISSING; + if (lock) - sfx->locks++; + S_LockSfx (sfx); if (!nosound.integer && snd_precache.integer) S_LoadSound(sfx, complain); @@ -371,21 +969,62 @@ sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean stdpath, q return sfx; } +/* +================== +S_IsSoundPrecached +================== +*/ +qboolean S_IsSoundPrecached (const sfx_t *sfx) +{ + return (sfx != NULL && sfx->fetcher != NULL); +} + +/* +================== +S_LockSfx + +Add a lock to a SFX +================== +*/ +void S_LockSfx (sfx_t *sfx) +{ + sfx->locks++; +} + /* ================== S_UnlockSfx -Remove a lock from a SFX and freed it if possible +Remove a lock from a SFX ================== */ void S_UnlockSfx (sfx_t *sfx) { sfx->locks--; - S_FreeSfx (sfx); } -//============================================================================= +/* +================== +S_BlockSound +================== +*/ +void S_BlockSound (void) +{ + snd_blocked++; +} + + +/* +================== +S_UnblockSound +================== +*/ +void S_UnblockSound (void) +{ + snd_blocked--; +} + /* ================= @@ -398,35 +1037,50 @@ channel_t *SND_PickChannel(int entnum, int entchannel) { int ch_idx; int first_to_die; - int life_left; + int first_life_left, life_left; channel_t* ch; // Check for replacement sound, or find the best one to replace first_to_die = -1; - life_left = 0x7fffffff; + first_life_left = 0x7fffffff; for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) { ch = &channels[ch_idx]; - if (entchannel != 0 // channel 0 never overrides - && ch->entnum == entnum - && (ch->entchannel == entchannel || entchannel == -1) ) - { // always override sound from same entity - first_to_die = ch_idx; - break; + if (entchannel != 0) + { + // try to override an existing channel + if (ch->entnum == entnum && (ch->entchannel == entchannel || entchannel == -1) ) + { + // always override sound from same entity + first_to_die = ch_idx; + break; + } + } + else + { + if (!ch->sfx) + { + // no sound on this channel + first_to_die = ch_idx; + break; + } } - // don't let monster sounds override player sounds - if (ch->entnum == cl.viewentity && entnum != cl.viewentity && ch->sfx) - continue; + if (ch->sfx) + { + // don't let monster sounds override player sounds + if (ch->entnum == cl.viewentity && entnum != cl.viewentity) + continue; - // don't override looped sounds - if ((ch->flags & CHANNELFLAG_FORCELOOP) != 0 || - (ch->sfx != NULL && ch->sfx->loopstart >= 0)) - continue; + // don't override looped sounds + if ((ch->flags & CHANNELFLAG_FORCELOOP) || ch->sfx->loopstart >= 0) + continue; + } - if (ch->end - paintedtime < life_left) + life_left = (int)(ch->end - snd_renderbuffer->endframe); + if (life_left < first_life_left) { - life_left = ch->end - paintedtime; + first_life_left = life_left; first_to_die = ch_idx; } } @@ -448,49 +1102,54 @@ Spatializes a channel */ void SND_Spatialize(channel_t *ch, qboolean isstatic) { - vec_t dist, scale, pan; + int i; + vec_t dist, mastervol, intensity, vol; vec3_t source_vec; + // update sound origin if we know about the entity + if (ch->entnum > 0 && cls.state == ca_connected && cl.entities[ch->entnum].state_current.active) + { + //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); + VectorCopy(cl.entities[ch->entnum].state_current.origin, ch->origin); + if (cl.entities[ch->entnum].state_current.modelindex && cl.model_precache[cl.entities[ch->entnum].state_current.modelindex] && cl.model_precache[cl.entities[ch->entnum].state_current.modelindex]->soundfromcenter) + VectorMAMAM(1.0f, ch->origin, 0.5f, cl.model_precache[cl.entities[ch->entnum].state_current.modelindex]->normalmins, 0.5f, cl.model_precache[cl.entities[ch->entnum].state_current.modelindex]->normalmaxs, ch->origin); + } + + mastervol = ch->master_vol; + // Adjust volume of static sounds + if (isstatic) + mastervol *= snd_staticvolume.value; + // anything coming from the view entity will always be full volume // LordHavoc: make sounds with ATTN_NONE have no spatialization if (ch->entnum == cl.viewentity || ch->dist_mult == 0) { - ch->leftvol = ch->master_vol; - ch->rightvol = ch->master_vol; + for (i = 0;i < SND_LISTENERS;i++) + { + vol = mastervol * snd_speakerlayout.listeners[i].ambientvolume; + ch->listener_volume[i] = (int)bound(0, vol, 255); + } } else { - // update sound origin if we know about the entity - if (ch->entnum > 0 && cls.state == ca_connected && cl_entities[ch->entnum].state_current.active) + // calculate stereo seperation and distance attenuation + VectorSubtract(listener_origin, ch->origin, source_vec); + dist = VectorLength(source_vec); + intensity = mastervol * (1.0 - dist * ch->dist_mult); + if (intensity > 0) { - //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl_entities[ch->entnum].state_current.origin[0], cl_entities[ch->entnum].state_current.origin[1], cl_entities[ch->entnum].state_current.origin[2]); - VectorCopy(cl_entities[ch->entnum].state_current.origin, ch->origin); - if (cl_entities[ch->entnum].state_current.modelindex && cl.model_precache[cl_entities[ch->entnum].state_current.modelindex] && cl.model_precache[cl_entities[ch->entnum].state_current.modelindex]->soundfromcenter) - VectorMAMAM(1.0f, ch->origin, 0.5f, cl.model_precache[cl_entities[ch->entnum].state_current.modelindex]->normalmins, 0.5f, cl.model_precache[cl_entities[ch->entnum].state_current.modelindex]->normalmaxs, ch->origin); + for (i = 0;i < SND_LISTENERS;i++) + { + Matrix4x4_Transform(&listener_matrix[i], ch->origin, source_vec); + VectorNormalize(source_vec); + vol = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); + ch->listener_volume[i] = (int)bound(0, vol, 255); + } } - - // calculate stereo seperation and distance attenuation - Matrix4x4_Transform(&listener_matrix, ch->origin, source_vec); - dist = VectorNormalizeLength(source_vec); - // distance - scale = ch->master_vol * (1.0 - (dist * ch->dist_mult)); - // panning - pan = scale * source_vec[1]; - // calculate the volumes - ch->leftvol = (int) (scale + pan); - ch->rightvol = (int) (scale - pan); - } - - // Adjust volume of static sounds - if (isstatic) - { - ch->leftvol *= snd_staticvolume.value; - ch->rightvol *= snd_staticvolume.value; + else + for (i = 0;i < SND_LISTENERS;i++) + ch->listener_volume[i] = 0; } - - // clamp volumes - ch->leftvol = bound(0, ch->leftvol, 255); - ch->rightvol = bound(0, ch->rightvol, 255); } @@ -503,10 +1162,10 @@ void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, // Initialize the channel memset (target_chan, 0, sizeof (*target_chan)); VectorCopy (origin, target_chan->origin); - target_chan->master_vol = fvol * 255; + target_chan->master_vol = (int)(fvol * 255); target_chan->sfx = sfx; - target_chan->end = paintedtime + sfx->total_length; - target_chan->lastptime = paintedtime; + target_chan->end = snd_renderbuffer->endframe + sfx->total_length; + target_chan->lastptime = snd_renderbuffer->endframe; target_chan->flags = flags; // If it's a static sound @@ -514,13 +1173,13 @@ void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, { if (sfx->loopstart == -1) Con_DPrintf("Quake compatibility warning: Static sound \"%s\" is not looped\n", sfx->name); - target_chan->dist_mult = attenuation / (64.0f * sound_nominal_clip_dist); + target_chan->dist_mult = attenuation / (64.0f * snd_soundradius.value); } else - target_chan->dist_mult = attenuation / sound_nominal_clip_dist; + target_chan->dist_mult = attenuation / snd_soundradius.value; // Lock the SFX during play - sfx->locks++; + S_LockSfx (sfx); } @@ -528,19 +1187,22 @@ int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f { channel_t *target_chan, *check; int ch_idx; - size_t skip; + int skip; - if (!sound_started || !sfx || !sfx->fetcher || nosound.integer) + if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) return -1; + if (sfx->fetcher == NULL) + return -1; + + if (entnum && entnum >= cl.max_entities) + CL_ExpandEntities(entnum); + // Pick a channel to play on target_chan = SND_PickChannel(entnum, entchannel); if (!target_chan) return -1; - if (!S_LoadSound (sfx, true)) - return -1; // couldn't load the sound's data - S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_NONE, origin, fvol, attenuation, false); target_chan->entnum = entnum; target_chan->entchannel = entchannel; @@ -556,9 +1218,9 @@ int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f continue; if (check->sfx == sfx && !check->pos) { - skip = 0.1 * sfx->format.speed; - if (skip > sfx->total_length) - skip = sfx->total_length; + skip = (int)(0.1 * snd_renderbuffer->format.speed); + if (skip > (int)sfx->total_length) + skip = (int)sfx->total_length; if (skip > 0) skip = rand() % skip; target_chan->pos += skip; @@ -584,9 +1246,9 @@ void S_StopChannel (unsigned int channel_ind) if (sfx->fetcher != NULL) { - snd_fetcher_end_t fetcher_end = sfx->fetcher->end; - if (fetcher_end != NULL) - fetcher_end (ch); + snd_fetcher_endsb_t fetcher_endsb = sfx->fetcher->endsb; + if (fetcher_endsb != NULL) + fetcher_endsb (ch); } // Remove the lock it holds @@ -628,17 +1290,37 @@ void S_StopSound(int entnum, int entchannel) } } +extern void CDAudio_Stop(void); void S_StopAllSounds (void) { unsigned int i; + // TOCHECK: is this test necessary? + if (snd_renderbuffer == NULL) + return; + + // stop CD audio because it may be using a faketrack + CDAudio_Stop(); + for (i = 0; i < total_channels; i++) S_StopChannel (i); total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); - S_ClearBuffer (); + // Mute the contents of the submittion buffer + if (simsound || SndSys_LockRenderBuffer ()) + { + int clear; + size_t memsize; + + clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; + memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; + memset(snd_renderbuffer->ring, clear, memsize); + + if (!simsound) + SndSys_UnlockRenderBuffer (); + } } void S_PauseGameSounds (qboolean toggle) @@ -657,38 +1339,7 @@ void S_PauseGameSounds (qboolean toggle) void S_SetChannelVolume (unsigned int ch_ind, float fvol) { - channels[ch_ind].master_vol = fvol * 255; -} - - -void S_ClearBuffer(void) -{ - int clear; - unsigned char *pbuf; - - if (!sound_started || !shm) - return; - - if (shm->format.width == 1) - clear = 0x80; // 8 bit sound (unsigned) - else - clear = 0; // 16 bit sound (signed) - - pbuf = S_LockBuffer(); - if (pbuf != NULL) - { - int setsize = shm->samples * shm->format.width; - - while (setsize--) - *pbuf++ = clear; - - // on i586/i686 optimized versions of glibc, glibc *wrongly* IMO, - // reads the memory area before writing to it causing a seg fault - // since the memory is PROT_WRITE only and not PROT_READ|PROT_WRITE - //memset(shm->buffer, clear, shm->samples * shm->format.width); - - S_UnlockBuffer (); - } + channels[ch_ind].master_vol = (int)(fvol * 255.0f); } @@ -701,17 +1352,19 @@ void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) { channel_t *target_chan; - if (!sfx) + if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) return; - - if (total_channels == MAX_CHANNELS) + if (!sfx->fetcher) { - Con_Print("total_channels == MAX_CHANNELS\n"); + Con_DPrintf ("S_StaticSound: \"%s\" hasn't been precached\n", sfx->name); return; } - if (!S_LoadSound (sfx, true)) + if (total_channels == MAX_CHANNELS) + { + Con_Print("S_StaticSound: total_channels == MAX_CHANNELS\n"); return; + } target_chan = &channels[total_channels++]; S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true); @@ -720,59 +1373,117 @@ void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) } -//============================================================================= - /* =================== S_UpdateAmbientSounds - =================== */ void S_UpdateAmbientSounds (void) { - float vol; + int i; + int vol; int ambient_channel; channel_t *chan; - qbyte ambientlevels[NUM_AMBIENTS]; - - // Mute ambient sounds until proven otherwise - for (ambient_channel = 0 ; ambient_channel < NUM_AMBIENTS;ambient_channel++) - channels[ambient_channel].master_vol = 0; + unsigned char ambientlevels[NUM_AMBIENTS]; - if (ambient_level.value <= 0 || !cl.worldmodel || !cl.worldmodel->brush.AmbientSoundLevelsForPoint) - return; - - cl.worldmodel->brush.AmbientSoundLevelsForPoint(cl.worldmodel, listener_origin, ambientlevels, sizeof(ambientlevels)); + memset(ambientlevels, 0, sizeof(ambientlevels)); + if (cl.worldmodel && cl.worldmodel->brush.AmbientSoundLevelsForPoint) + cl.worldmodel->brush.AmbientSoundLevelsForPoint(cl.worldmodel, listener_origin, ambientlevels, sizeof(ambientlevels)); // Calc ambient sound levels for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) { chan = &channels[ambient_channel]; - if (chan->sfx == NULL || (chan->sfx->flags & SFXFLAG_FILEMISSING)) + if (chan->sfx == NULL || chan->sfx->fetcher == NULL) continue; - vol = ambient_level.value * ambientlevels[ambient_channel]; + vol = (int)ambientlevels[ambient_channel]; if (vol < 8) vol = 0; // Don't adjust volume too fast + // FIXME: this rounds off to an int each frame, meaning there is little to no fade at extremely high framerates! if (chan->master_vol < vol) { - chan->master_vol += host_realframetime * ambient_fade.value; + chan->master_vol += (int)(cl.realframetime * ambient_fade.value); if (chan->master_vol > vol) chan->master_vol = vol; } else if (chan->master_vol > vol) { - chan->master_vol -= host_realframetime * ambient_fade.value; + chan->master_vol -= (int)(cl.realframetime * ambient_fade.value); if (chan->master_vol < vol) chan->master_vol = vol; } - chan->leftvol = chan->rightvol = chan->master_vol; + for (i = 0;i < SND_LISTENERS;i++) + chan->listener_volume[i] = (int)(chan->master_vol * ambient_level.value * snd_speakerlayout.listeners[i].ambientvolume); } } +static void S_PaintAndSubmit (void) +{ + unsigned int newsoundtime, paintedtime, endtime, maxtime, usedframes; + + if (snd_renderbuffer == NULL || nosound.integer) + return; + + if (snd_blocked > 0 && !(cls.capturevideo.soundrate && !cls.capturevideo.realtime)) + return; + + // Update sound time + if (cls.capturevideo.soundrate && !cls.capturevideo.realtime) // SUPER NASTY HACK to record non-realtime sound + newsoundtime = (unsigned int)((double)cls.capturevideo.frame * (double)snd_renderbuffer->format.speed / (double)cls.capturevideo.framerate); + else if (simsound) + newsoundtime = (unsigned int)((realtime - snd_starttime) * (double)snd_renderbuffer->format.speed); + else + newsoundtime = SndSys_GetSoundTime(); + + newsoundtime += extrasoundtime; + if (newsoundtime < soundtime) + { + if ((cls.capturevideo.soundrate != 0) != recording_sound) + { + unsigned int additionaltime; + + // add some time to extrasoundtime make newsoundtime higher + + // The extra time must be a multiple of the render buffer size + // to avoid modifying the current position in the buffer, + // some modules write directly to a shared (DMA) buffer + additionaltime = (soundtime - newsoundtime) + snd_renderbuffer->maxframes - 1; + additionaltime -= additionaltime % snd_renderbuffer->maxframes; + + extrasoundtime += additionaltime; + newsoundtime += additionaltime; + Con_DPrintf("S_PaintAndSubmit: new extra sound time = %u\n", + extrasoundtime); + } + else + Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n", + newsoundtime, soundtime); + } + soundtime = newsoundtime; + recording_sound = (cls.capturevideo.soundrate != 0); + + // Check to make sure that we haven't overshot + paintedtime = snd_renderbuffer->endframe; + if (paintedtime < soundtime) + paintedtime = soundtime; + + // mix ahead of current position + endtime = soundtime + (unsigned int)(_snd_mixahead.value * (float)snd_renderbuffer->format.speed); + usedframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; + maxtime = paintedtime + snd_renderbuffer->maxframes - usedframes; + endtime = min(endtime, maxtime); + + S_PaintChannels(snd_renderbuffer, paintedtime, endtime); + + if (simsound) + snd_renderbuffer->startframe = snd_renderbuffer->endframe; + else + SndSys_Submit(); +} /* ============ @@ -785,13 +1496,39 @@ void S_Update(const matrix4x4_t *listenermatrix) { unsigned int i, j, total; channel_t *ch, *combine; + matrix4x4_t basematrix, rotatematrix; + + if (snd_renderbuffer == NULL || nosound.integer) + return; - if (!snd_initialized.integer || (snd_blocked > 0)) + if (snd_blocked > 0 && !(cls.capturevideo.soundrate && !cls.capturevideo.realtime)) return; - Matrix4x4_Invert_Simple(&listener_matrix, listenermatrix); + // If snd_swapstereo or snd_channellayout has changed, recompute the channel layout + if (current_swapstereo != snd_swapstereo.integer || + current_channellayout != snd_channellayout.integer) + S_SetChannelLayout(); + + Matrix4x4_Invert_Simple(&basematrix, listenermatrix); Matrix4x4_OriginFromMatrix(listenermatrix, listener_origin); + // calculate the current matrices + for (j = 0;j < SND_LISTENERS;j++) + { + Matrix4x4_CreateFromQuakeEntity(&rotatematrix, 0, 0, 0, 0, -snd_speakerlayout.listeners[j].yawangle, 0, 1); + Matrix4x4_Concat(&listener_matrix[j], &rotatematrix, &basematrix); + // I think this should now do this: + // 1. create a rotation matrix for rotating by e.g. -90 degrees CCW + // (note: the matrix will rotate the OBJECT, not the VIEWER, so its + // angle has to be taken negative) + // 2. create a transform which first rotates and moves its argument + // into the player's view coordinates (using basematrix which is + // an inverted "absolute" listener matrix), then applies the + // rotation matrix for the ear + // Isn't Matrix4x4_CreateFromQuakeEntity a bit misleading because this + // does not actually refer to an entity? + } + // update general area ambient sound sources S_UpdateAmbientSounds (); @@ -803,39 +1540,41 @@ void S_Update(const matrix4x4_t *listenermatrix) { if (!ch->sfx) continue; - SND_Spatialize(ch, i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS); // respatialize channel - if (!ch->leftvol && !ch->rightvol) - continue; + + // respatialize channel + SND_Spatialize(ch, i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS); // try to combine static sounds with a previous channel of the same // sound effect so we don't mix five torches every frame if (i > MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS) { - // see if it can just use the last one - if (combine && combine->sfx == ch->sfx) - { - combine->leftvol += ch->leftvol; - combine->rightvol += ch->rightvol; - ch->leftvol = ch->rightvol = 0; - continue; - } - // search for one - combine = channels+MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; - for (j=MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS ; jsfx == ch->sfx) + // no need to merge silent channels + for (j = 0;j < SND_LISTENERS;j++) + if (ch->listener_volume[j]) break; - - if (j == total_channels) + if (j == SND_LISTENERS) + continue; + // if the last combine chosen isn't suitable, find a new one + if (!(combine && combine != ch && combine->sfx == ch->sfx)) + { + // search for one combine = NULL; - else + for (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;j < i;j++) + { + if (channels[j].sfx == ch->sfx) + { + combine = channels + j; + break; + } + } + } + if (combine && combine != ch && combine->sfx == ch->sfx) { - if (combine != ch) + for (j = 0;j < SND_LISTENERS;j++) { - combine->leftvol += ch->leftvol; - combine->rightvol += ch->rightvol; - ch->leftvol = ch->rightvol = 0; + combine->listener_volume[j] += ch->listener_volume[j]; + ch->listener_volume[j] = 0; } - continue; } } } @@ -848,42 +1587,21 @@ void S_Update(const matrix4x4_t *listenermatrix) total = 0; ch = channels; for (i=0 ; isfx && (ch->leftvol || ch->rightvol) ) - total++; + { + if (ch->sfx) + { + for (j = 0;j < SND_LISTENERS;j++) + if (ch->listener_volume[j]) + break; + if (j < SND_LISTENERS) + total++; + } + } Con_Printf("----(%u)----\n", total); } - S_Update_(); -} - -void GetSoundtime(void) -{ - int samplepos; - static int buffers; - static int oldsamplepos; - int fullsamples; - - fullsamples = shm->samples / shm->format.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 / shm->format.channels; + S_PaintAndSubmit(); } void S_ExtraUpdate (void) @@ -891,119 +1609,10 @@ void S_ExtraUpdate (void) if (snd_noextraupdate.integer || !sound_spatialized) return; - S_Update_(); -} - -void S_Update_(void) -{ - unsigned endtime; - int samps; - - if (!sound_started || (snd_blocked > 0)) - return; - - // Updates DMA time - GetSoundtime(); - - // check to make sure that we haven't overshot - if (paintedtime < soundtime) - paintedtime = soundtime; - - // mix ahead of current position - endtime = soundtime + _snd_mixahead.value * shm->format.speed; - samps = shm->samples >> (shm->format.channels - 1); - if (endtime > (unsigned int)(soundtime + samps)) - endtime = soundtime + samps; - - S_PaintChannels (endtime); - - SNDDMA_Submit (); -} - -/* -=============================================================================== - -console functions - -=============================================================================== -*/ - -static void S_Play_Common(float fvol, float attenuation) -{ - int i, ch_ind; - char name[256]; - sfx_t *sfx; - - i = 1; - while (inext, i++) - { - if (sfx->fetcher != NULL) - { - size = sfx->mempool->totalsize; - total += size; - Con_Printf ("%c%c(%2db, %6s) %8i : %s\n", - (sfx->loopstart >= 0) ? 'L' : ' ', - (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', - sfx->format.width * 8, - (sfx->format.channels == 1) ? "mono" : "stereo", - size, - sfx->name); - } - } - Con_Printf("Total resident: %i\n", total); + S_PaintAndSubmit(); } - -qboolean S_LocalSound (const char *sound, qboolean stdpath) +qboolean S_LocalSound (const char *sound) { sfx_t *sfx; int ch_ind; @@ -1011,14 +1620,17 @@ qboolean S_LocalSound (const char *sound, qboolean stdpath) if (!snd_initialized.integer || nosound.integer) return true; - sfx = S_PrecacheSound (sound, true, stdpath, false); + sfx = S_PrecacheSound (sound, true, false); if (!sfx) { Con_Printf("S_LocalSound: can't precache %s\n", sound); return false; } - ch_ind = S_StartSound (cl.viewentity, 0, sfx, vec3_origin, 1, 1); + // Local sounds must not be freed + sfx->flags |= SFXFLAG_PERMANENTLOCK; + + ch_ind = S_StartSound (cl.viewentity, 0, sfx, vec3_origin, 1, 0); if (ch_ind < 0) return false;