X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=snd_main.c;h=540a7f9e7c8f140e0978eedc5aaf277b68f0c902;hp=86cb8e7a565e789362f72ba432c88bb8275eb210;hb=621e9a6a36d11a2906817008ef4233cff86657cb;hpb=844bad4f7cddcc970715f9fc9d22d74896cc9df4 diff --git a/snd_main.c b/snd_main.c index 86cb8e7a..540a7f9e 100644 --- a/snd_main.c +++ b/snd_main.c @@ -23,6 +23,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "snd_main.h" #include "snd_ogg.h" +#include "snd_modplug.h" +#include "csprogs.h" #define SND_MIN_SPEED 8000 @@ -31,30 +33,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #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; -static speakerlayout_t snd_speakerlayout; +speakerlayout_t snd_speakerlayout; // Our speaker layouts are based on ALSA. They differ from those -// Win32 APIs and Mac OS X use when there's more than 4 channels. +// 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[] = @@ -62,27 +48,27 @@ 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 + {0, 45, 0.2, 0.2, 0.5}, // front left + {1, 315, 0.2, 0.2, 0.5}, // front right + {2, 135, 0.2, 0.2, 0.5}, // rear left + {3, 225, 0.2, 0.2, 0.5}, // rear right + {4, 0, 0.2, 0.2, 0.5}, // front center + {5, 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) + {6, 90, 0.2, 0.2, 0.5}, // side left + {7, 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}, + {0, 45, 0.2, 0.2, 0.5}, // front left + {1, 315, 0.2, 0.2, 0.5}, // front right + {2, 135, 0.2, 0.2, 0.5}, // rear left + {3, 225, 0.2, 0.2, 0.5}, // rear right + {4, 0, 0.2, 0.2, 0.5}, // front center + {5, 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, 0, 0}, } }, { @@ -90,14 +76,14 @@ static const speakerlayout_t snd_speakerlayouts[] = // 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}, + {0, 45, 0.3, 0.3, 0.8}, // front left + {1, 315, 0.3, 0.3, 0.8}, // front right + {2, 135, 0.3, 0.3, 0.8}, // rear left + {3, 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, 0, 0, 0, 0}, } }, { @@ -105,27 +91,27 @@ static const speakerlayout_t snd_speakerlayouts[] = // 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}, + {0, 90, 0.5, 0.5, 1}, // side left + {1, 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, 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}, + {0, 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, 0, 0}, + {0, 0, 0, 0, 0}, } } }; @@ -139,14 +125,15 @@ channel_t channels[MAX_CHANNELS]; unsigned int total_channels; snd_ringbuffer_t *snd_renderbuffer = NULL; -unsigned int soundtime = 0; +static unsigned int soundtime = 0; static unsigned int oldpaintedtime = 0; -unsigned int extrasoundtime = 0; +static unsigned int extrasoundtime = 0; static double snd_starttime = 0.0; +qboolean snd_threaded = false; +qboolean snd_usethreadedmixing = false; vec3_t listener_origin; matrix4x4_t listener_matrix[SND_LISTENERS]; -vec_t sound_nominal_clip_dist=1000.0; mempool_t *snd_mempool; // Linked list of known sfx @@ -155,21 +142,71 @@ static sfx_t *known_sfx = NULL; static qboolean sound_spatialized = false; qboolean simsound = false; -qboolean alsaspeakerlayout = false; + +static qboolean recording_sound = false; int snd_blocked = 0; -static int current_swapstereo = 0; +static int current_swapstereo = false; +static int current_channellayout = SND_CHANNELLAYOUT_AUTO; +static int current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + +static float spatialpower, spatialmin, spatialdiff, spatialoffset, spatialfactor; +typedef enum { SPATIAL_NONE, SPATIAL_LOG, SPATIAL_POW, SPATIAL_THRESH } spatialmethod_t; +spatialmethod_t spatialmethod; // 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 = {CVAR_SAVE, "snd_soundradius", "2000", "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)"}; +cvar_t snd_spatialization_min_radius = {CVAR_SAVE, "snd_spatialization_min_radius", "10000", "use minimum spatialization above to this radius"}; +cvar_t snd_spatialization_max_radius = {CVAR_SAVE, "snd_spatialization_max_radius", "100", "use maximum spatialization below this radius"}; +cvar_t snd_spatialization_min = {CVAR_SAVE, "snd_spatialization_min", "0.70", "minimum spatializazion of sounds"}; +cvar_t snd_spatialization_max = {CVAR_SAVE, "snd_spatialization_max", "0.95", "maximum spatialization of sounds"}; +cvar_t snd_spatialization_power = {CVAR_SAVE, "snd_spatialization_power", "0", "exponent of the spatialization falloff curve (0: logarithmic)"}; +cvar_t snd_spatialization_control = {CVAR_SAVE, "snd_spatialization_control", "0", "enable spatialization control (headphone friendly mode)"}; +cvar_t snd_spatialization_occlusion = {CVAR_SAVE, "snd_spatialization_occlusion", "1", "enable occlusion testing on spatialized sounds, which simply quiets sounds that are blocked by the world"}; // 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_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.11", "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"}; +extern cvar_t v_flipped; +cvar_t snd_channellayout = {0, "snd_channellayout", "0", "channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout)"}; +cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; +cvar_t snd_entchannel0volume = {CVAR_SAVE, "snd_entchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of regular entities"}; +cvar_t snd_entchannel1volume = {CVAR_SAVE, "snd_entchannel1volume", "1", "volume multiplier of the 1st entity channel of regular entities"}; +cvar_t snd_entchannel2volume = {CVAR_SAVE, "snd_entchannel2volume", "1", "volume multiplier of the 2nd entity channel of regular entities"}; +cvar_t snd_entchannel3volume = {CVAR_SAVE, "snd_entchannel3volume", "1", "volume multiplier of the 3rd entity channel of regular entities"}; +cvar_t snd_entchannel4volume = {CVAR_SAVE, "snd_entchannel4volume", "1", "volume multiplier of the 4th entity channel of regular entities"}; +cvar_t snd_entchannel5volume = {CVAR_SAVE, "snd_entchannel5volume", "1", "volume multiplier of the 5th entity channel of regular entities"}; +cvar_t snd_entchannel6volume = {CVAR_SAVE, "snd_entchannel6volume", "1", "volume multiplier of the 6th entity channel of regular entities"}; +cvar_t snd_entchannel7volume = {CVAR_SAVE, "snd_entchannel7volume", "1", "volume multiplier of the 7th entity channel of regular entities"}; +cvar_t snd_playerchannel0volume = {CVAR_SAVE, "snd_playerchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of player entities"}; +cvar_t snd_playerchannel1volume = {CVAR_SAVE, "snd_playerchannel1volume", "1", "volume multiplier of the 1st entity channel of player entities"}; +cvar_t snd_playerchannel2volume = {CVAR_SAVE, "snd_playerchannel2volume", "1", "volume multiplier of the 2nd entity channel of player entities"}; +cvar_t snd_playerchannel3volume = {CVAR_SAVE, "snd_playerchannel3volume", "1", "volume multiplier of the 3rd entity channel of player entities"}; +cvar_t snd_playerchannel4volume = {CVAR_SAVE, "snd_playerchannel4volume", "1", "volume multiplier of the 4th entity channel of player entities"}; +cvar_t snd_playerchannel5volume = {CVAR_SAVE, "snd_playerchannel5volume", "1", "volume multiplier of the 5th entity channel of player entities"}; +cvar_t snd_playerchannel6volume = {CVAR_SAVE, "snd_playerchannel6volume", "1", "volume multiplier of the 6th entity channel of player entities"}; +cvar_t snd_playerchannel7volume = {CVAR_SAVE, "snd_playerchannel7volume", "1", "volume multiplier of the 7th entity channel of player entities"}; +cvar_t snd_worldchannel0volume = {CVAR_SAVE, "snd_worldchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of the world entity"}; +cvar_t snd_worldchannel1volume = {CVAR_SAVE, "snd_worldchannel1volume", "1", "volume multiplier of the 1st entity channel of the world entity"}; +cvar_t snd_worldchannel2volume = {CVAR_SAVE, "snd_worldchannel2volume", "1", "volume multiplier of the 2nd entity channel of the world entity"}; +cvar_t snd_worldchannel3volume = {CVAR_SAVE, "snd_worldchannel3volume", "1", "volume multiplier of the 3rd entity channel of the world entity"}; +cvar_t snd_worldchannel4volume = {CVAR_SAVE, "snd_worldchannel4volume", "1", "volume multiplier of the 4th entity channel of the world entity"}; +cvar_t snd_worldchannel5volume = {CVAR_SAVE, "snd_worldchannel5volume", "1", "volume multiplier of the 5th entity channel of the world entity"}; +cvar_t snd_worldchannel6volume = {CVAR_SAVE, "snd_worldchannel6volume", "1", "volume multiplier of the 6th entity channel of the world entity"}; +cvar_t snd_worldchannel7volume = {CVAR_SAVE, "snd_worldchannel7volume", "1", "volume multiplier of the 7th entity channel of the world entity"}; +cvar_t snd_csqcchannel0volume = {CVAR_SAVE, "snd_csqcchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of the world entity"}; +cvar_t snd_csqcchannel1volume = {CVAR_SAVE, "snd_csqcchannel1volume", "1", "volume multiplier of the 1st entity channel of the world entity"}; +cvar_t snd_csqcchannel2volume = {CVAR_SAVE, "snd_csqcchannel2volume", "1", "volume multiplier of the 2nd entity channel of the world entity"}; +cvar_t snd_csqcchannel3volume = {CVAR_SAVE, "snd_csqcchannel3volume", "1", "volume multiplier of the 3rd entity channel of the world entity"}; +cvar_t snd_csqcchannel4volume = {CVAR_SAVE, "snd_csqcchannel4volume", "1", "volume multiplier of the 4th entity channel of the world entity"}; +cvar_t snd_csqcchannel5volume = {CVAR_SAVE, "snd_csqcchannel5volume", "1", "volume multiplier of the 5th entity channel of the world entity"}; +cvar_t snd_csqcchannel6volume = {CVAR_SAVE, "snd_csqcchannel6volume", "1", "volume multiplier of the 6th entity channel of the world entity"}; +cvar_t snd_csqcchannel7volume = {CVAR_SAVE, "snd_csqcchannel7volume", "1", "volume multiplier of the 7th entity channel of the world entity"}; // Local cvars static cvar_t nosound = {0, "nosound", "0", "disables sound"}; @@ -218,7 +255,7 @@ static void S_Play_Common (float fvol, float attenuation) i++; } - sfx = S_PrecacheSound (name, true, false); + sfx = S_PrecacheSound (name, true, true); if (sfx) { ch_ind = S_StartSound (-1, 0, sfx, listener_origin, fvol, attenuation); @@ -264,7 +301,7 @@ static void S_SoundList_f (void) size = sfx->memsize; format = sfx->fetcher->getfmt(sfx); Con_Printf ("%c%c%c%c(%2db, %6s) %8i : %s\n", - (sfx->loopstart >= 0) ? 'L' : ' ', + (sfx->loopstart < sfx->total_length) ? 'L' : ' ', (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', (sfx->locks > 0) ? 'K' : ' ', (sfx->flags & SFXFLAG_PERMANENTLOCK) ? 'P' : ' ', @@ -297,71 +334,103 @@ void S_SoundInfo_f(void) } -// TODO: make this function smarter... -static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels) +int S_GetSoundRate(void) { - // Can we decrease the number of channels? - if (!fixed_channels && format->channels > 1) - { - unsigned short channels = format->channels; + return snd_renderbuffer ? snd_renderbuffer->format.speed : 0; +} - // If it has an odd number of channels(?!), make it even - if (channels & 1) - channels--; - else - { - // Remove 2 speakers, unless it's a stereo format - if (channels != 2) - channels -= 2; - else - channels = 1; - } +int S_GetSoundChannels(void) +{ + return snd_renderbuffer ? snd_renderbuffer->format.channels : 0; +} - format->channels = channels; - return true; - } - // Can we decrease the speed? - if (!fixed_speed) +static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels) +{ + static const snd_format_t thresholds [] = { - unsigned int suggest_speeds [] = { 44100, 22050, 11025 }; - unsigned int i; - - for (i = 0; i < sizeof(suggest_speeds) / sizeof(suggest_speeds[0]); i++) - if (format->speed > suggest_speeds[i]) - { - format->speed = suggest_speeds[i]; - return true; - } + // speed width channels + { SND_MIN_SPEED, SND_MIN_WIDTH, SND_MIN_CHANNELS }, + { 11025, 1, 2 }, + { 22050, 2, 2 }, + { 44100, 2, 2 }, + { 48000, 2, 6 }, + { 96000, 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; - // the speed is already low + // 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 - // Can we decrease the number of bits per sample? - if (!fixed_width && format->width > 1) + // 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->width = 1; + format->speed = thresholds[speed_level - 1].speed; return true; } - return false; + format->width = thresholds[width_level - 1].width; + return true; } #define SWAP_LISTENERS(l1, l2, tmpl) { tmpl = (l1); (l1) = (l2); (l2) = tmpl; } -void S_SetSpeakerLayout (void) +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_SetSpeakerLayout: Can't find the speaker layout for %hu channels. Defaulting to mono output\n", + 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; } @@ -370,7 +439,7 @@ void S_SetSpeakerLayout (void) listeners = snd_speakerlayout.listeners; // Swap the left and right channels if snd_swapstereo is set - if (snd_swapstereo.integer) + if (boolxor(snd_swapstereo.integer, v_flipped.integer)) { switch (snd_speakerlayout.channels) { @@ -392,13 +461,41 @@ void S_SetSpeakerLayout (void) } } - // Convert our layout (= ALSA) to Win32/CoreAudio layout if necessary - if (!alsaspeakerlayout && - (snd_speakerlayout.channels == 6 || snd_speakerlayout.channels == 8)) + // 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) { - SWAP_LISTENERS(listeners[2], listeners[4], swaplistener); - SWAP_LISTENERS(listeners[3], listeners[5], swaplistener); + // 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 = boolxor(snd_swapstereo.integer, v_flipped.integer); + current_channellayout = snd_channellayout.integer; + current_channellayout_used = layout; } @@ -407,7 +504,10 @@ 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; + char* env; +#if _MSC_VER >= 1400 + size_t envlen; +#endif int i; if (!snd_initialized.integer) @@ -423,22 +523,43 @@ void S_Startup (void) chosen_fmt.channels = snd_channels.integer; // Check the environment variables to see if the player wants a particular sound format +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_CHANNELS"); +#else env = getenv("QUAKE_SOUND_CHANNELS"); +#endif if (env != NULL) { chosen_fmt.channels = atoi (env); +#if _MSC_VER >= 1400 + free(env); +#endif fixed_channels = true; } +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_SPEED"); +#else env = getenv("QUAKE_SOUND_SPEED"); +#endif if (env != NULL) { chosen_fmt.speed = atoi (env); +#if _MSC_VER >= 1400 + free(env); +#endif fixed_speed = true; } +#if _MSC_VER >= 1400 + _dupenv_s(&env, &envlen, "QUAKE_SOUND_SAMPLEBITS"); +#else env = getenv("QUAKE_SOUND_SAMPLEBITS"); +#endif if (env != NULL) { chosen_fmt.width = atoi (env) / 8; +#if _MSC_VER >= 1400 + free(env); +#endif fixed_width = true; } @@ -531,17 +652,16 @@ void S_Startup (void) accepted = false; do { - Con_DPrintf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n", + Con_Printf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n", chosen_fmt.speed, chosen_fmt.width * 8, chosen_fmt.channels); - alsaspeakerlayout = false; 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"); + Con_Printf("S_Startup: sound output initialization FAILED\n"); // If the module is suggesting another one if (suggest_fmt.speed != 0) @@ -575,14 +695,16 @@ void S_Startup (void) Con_Printf("Sound format: %dHz, %d channels, %d bits per sample\n", chosen_fmt.speed, chosen_fmt.channels, chosen_fmt.width * 8); - if (chosen_fmt.channels > 4) - Con_Printf("Using %s speaker layout for 3D sound\n", - alsaspeakerlayout ? "ALSA" : "standard"); - // Update the cvars - snd_speed.integer = chosen_fmt.speed; - snd_width.integer = chosen_fmt.width; - snd_channels.integer = chosen_fmt.channels; + 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); + + current_channellayout_used = SND_CHANNELLAYOUT_AUTO; + S_SetChannelLayout(); snd_starttime = realtime; @@ -595,7 +717,7 @@ void S_Startup (void) // 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); + Con_Printf("S_Startup: extra sound time = %u\n", extrasoundtime); soundtime = extrasoundtime; } @@ -603,9 +725,7 @@ void S_Startup (void) extrasoundtime = 0; snd_renderbuffer->startframe = soundtime; snd_renderbuffer->endframe = soundtime; - - S_SetSpeakerLayout(); - current_swapstereo = snd_swapstereo.integer; + recording_sound = false; } void S_Shutdown(void) @@ -629,6 +749,14 @@ void S_Shutdown(void) void S_Restart_f(void) { + // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) + // So, refuse to do this if we are connected. + if(cls.state == ca_connected) + { + Con_Printf("snd_restart would wreak havoc if you do that while connected!\n"); + return; + } + S_Shutdown(); S_Startup(); } @@ -640,19 +768,63 @@ S_Init */ void S_Init(void) { - Con_DPrint("\nSound Initialization\n"); - Cvar_RegisterVariable(&volume); Cvar_RegisterVariable(&bgmvolume); Cvar_RegisterVariable(&snd_staticvolume); + Cvar_RegisterVariable(&snd_entchannel0volume); + Cvar_RegisterVariable(&snd_entchannel1volume); + Cvar_RegisterVariable(&snd_entchannel2volume); + Cvar_RegisterVariable(&snd_entchannel3volume); + Cvar_RegisterVariable(&snd_entchannel4volume); + Cvar_RegisterVariable(&snd_entchannel5volume); + Cvar_RegisterVariable(&snd_entchannel6volume); + Cvar_RegisterVariable(&snd_entchannel7volume); + Cvar_RegisterVariable(&snd_worldchannel0volume); + Cvar_RegisterVariable(&snd_worldchannel1volume); + Cvar_RegisterVariable(&snd_worldchannel2volume); + Cvar_RegisterVariable(&snd_worldchannel3volume); + Cvar_RegisterVariable(&snd_worldchannel4volume); + Cvar_RegisterVariable(&snd_worldchannel5volume); + Cvar_RegisterVariable(&snd_worldchannel6volume); + Cvar_RegisterVariable(&snd_worldchannel7volume); + Cvar_RegisterVariable(&snd_playerchannel0volume); + Cvar_RegisterVariable(&snd_playerchannel1volume); + Cvar_RegisterVariable(&snd_playerchannel2volume); + Cvar_RegisterVariable(&snd_playerchannel3volume); + Cvar_RegisterVariable(&snd_playerchannel4volume); + Cvar_RegisterVariable(&snd_playerchannel5volume); + Cvar_RegisterVariable(&snd_playerchannel6volume); + Cvar_RegisterVariable(&snd_playerchannel7volume); + Cvar_RegisterVariable(&snd_csqcchannel0volume); + Cvar_RegisterVariable(&snd_csqcchannel1volume); + Cvar_RegisterVariable(&snd_csqcchannel2volume); + Cvar_RegisterVariable(&snd_csqcchannel3volume); + Cvar_RegisterVariable(&snd_csqcchannel4volume); + Cvar_RegisterVariable(&snd_csqcchannel5volume); + Cvar_RegisterVariable(&snd_csqcchannel6volume); + Cvar_RegisterVariable(&snd_csqcchannel7volume); + + Cvar_RegisterVariable(&snd_spatialization_min_radius); + Cvar_RegisterVariable(&snd_spatialization_max_radius); + Cvar_RegisterVariable(&snd_spatialization_min); + Cvar_RegisterVariable(&snd_spatialization_max); + Cvar_RegisterVariable(&snd_spatialization_power); + Cvar_RegisterVariable(&snd_spatialization_control); + Cvar_RegisterVariable(&snd_spatialization_occlusion); Cvar_RegisterVariable(&snd_speed); Cvar_RegisterVariable(&snd_width); Cvar_RegisterVariable(&snd_channels); + Cvar_RegisterVariable(&snd_mutewhenidle); // COMMANDLINEOPTION: Sound: -nosound disables sound (including CD audio) - if (COM_CheckParm("-nosound") || COM_CheckParm("-safe")) + if (COM_CheckParm("-nosound")) + { + // dummy out Play and Play2 because mods stuffcmd that + Cmd_AddCommand("play", Host_NoOperation_f, "does nothing because -nosound was specified"); + Cmd_AddCommand("play2", Host_NoOperation_f, "does nothing because -nosound was specified"); return; + } snd_mempool = Mem_AllocPool("sound", 0, NULL); @@ -667,6 +839,7 @@ void S_Init(void) 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_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files"); Cvar_RegisterVariable(&nosound); Cvar_RegisterVariable(&snd_precache); @@ -678,6 +851,8 @@ void S_Init(void) 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); @@ -687,6 +862,7 @@ void S_Init(void) memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); OGG_OpenLibrary (); + ModPlug_OpenLibrary (); } @@ -700,6 +876,7 @@ Shutdown and free all resources void S_Terminate (void) { S_Shutdown (); + ModPlug_CloseLibrary (); OGG_CloseLibrary (); // Free all SFXs @@ -711,6 +888,36 @@ void S_Terminate (void) } +/* +================== +S_UnloadAllSounds_f +================== +*/ +void S_UnloadAllSounds_f (void) +{ + int i; + + // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) + // So, refuse to do this if we are connected. + if(cls.state == ca_connected) + { + Con_Printf("snd_unloadallsounds would wreak havoc if you do that while connected!\n"); + return; + } + + // 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 @@ -730,6 +937,7 @@ sfx_t *S_FindName (const char *name) } // 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, name)) return sfx; @@ -759,7 +967,8 @@ void S_FreeSfx (sfx_t *sfx, qboolean force) if (!force && (sfx->locks > 0 || (sfx->flags & SFXFLAG_PERMANENTLOCK))) return; - Con_DPrintf ("S_FreeSfx: freeing %s\n", sfx->name); + if (developer_loading.integer) + Con_Printf ("unloading sound %s\n", sfx->name); // Remove it from the list of known sfx if (sfx == known_sfx) @@ -784,24 +993,24 @@ void S_FreeSfx (sfx_t *sfx, qboolean force) // Stop all channels using this sfx for (i = 0; i < total_channels; i++) if (channels[i].sfx == sfx) - S_StopChannel (i); + S_StopChannel (i, true); // Free it if (sfx->fetcher != NULL && sfx->fetcher->free != NULL) - sfx->fetcher->free (sfx); + sfx->fetcher->free (sfx->fetcher_data); Mem_Free (sfx); } /* ================== -S_ServerSounds +S_ClearUsed ================== */ -void S_ServerSounds (char serversound [][MAX_QPATH], unsigned int numsounds) +void S_ClearUsed (void) { sfx_t *sfx; - sfx_t *sfxnext; +// sfx_t *sfxnext; unsigned int i; // Start the ambient sounds and make them loop @@ -829,17 +1038,17 @@ void S_ServerSounds (char serversound [][MAX_QPATH], unsigned int numsounds) 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]); - if (sfx != NULL) - { - S_LockSfx (sfx); - sfx->flags |= SFXFLAG_SERVERSOUND; - } - } +/* +================== +S_PurgeUnused +================== +*/ +void S_PurgeUnused(void) +{ + sfx_t *sfx; + sfx_t *sfxnext; // Free all unlocked sfx for (sfx = known_sfx;sfx;sfx = sfxnext) @@ -855,7 +1064,7 @@ void S_ServerSounds (char serversound [][MAX_QPATH], unsigned int numsounds) S_PrecacheSound ================== */ -sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean lock) +sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean serversound) { sfx_t *sfx; @@ -866,11 +1075,19 @@ sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean lock) return NULL; sfx = S_FindName (name); + if (sfx == NULL) return NULL; - if (lock) + // clear the FILEMISSING flag so that S_LoadSound will try again on a + // previously missing file + sfx->flags &= ~ SFXFLAG_FILEMISSING; + + if (serversound && !(sfx->flags & SFXFLAG_SERVERSOUND)) + { S_LockSfx (sfx); + sfx->flags |= SFXFLAG_SERVERSOUND; + } if (!nosound.integer && snd_precache.integer) S_LoadSound(sfx, complain); @@ -952,41 +1169,42 @@ channel_t *SND_PickChannel(int entnum, int entchannel) // Check for replacement sound, or find the best one to replace first_to_die = -1; first_life_left = 0x7fffffff; - for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + + // entity channels try to replace the existing sound on the channel + if (entchannel != 0) { - ch = &channels[ch_idx]; - if (entchannel != 0) + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) { - // try to override an existing channel + ch = &channels[ch_idx]; if (ch->entnum == entnum && (ch->entchannel == entchannel || entchannel == -1) ) { // always override sound from same entity - first_to_die = ch_idx; - break; + S_StopChannel (ch_idx, true); + return &channels[ch_idx]; } } - else + } + + // there was no channel to override, so look for the first empty one + for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) + { + ch = &channels[ch_idx]; + if (!ch->sfx) { - if (!ch->sfx) - { - // no sound on this channel - first_to_die = ch_idx; - break; - } + // no sound on this channel + first_to_die = ch_idx; + goto emptychan_found; } - if (ch->sfx) - { - // don't let monster sounds override player sounds - if (ch->entnum == cl.viewentity && entnum != cl.viewentity) - continue; + // 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) || ch->sfx->loopstart >= 0) - continue; - } + // don't override looped sounds + if ((ch->flags & CHANNELFLAG_FORCELOOP) || ch->sfx->loopstart < ch->sfx->total_length) + continue; + life_left = ch->sfx->total_length - ch->pos; - life_left = (int)(ch->end - snd_renderbuffer->endframe); if (life_left < first_life_left) { first_life_left = life_left; @@ -996,9 +1214,10 @@ channel_t *SND_PickChannel(int entnum, int entchannel) if (first_to_die == -1) return NULL; + + S_StopChannel (first_to_die, true); - S_StopChannel (first_to_die); - +emptychan_found: return &channels[first_to_die]; } @@ -1009,25 +1228,103 @@ SND_Spatialize Spatializes a channel ================= */ +extern cvar_t cl_gameplayfix_soundsmovewithentities; void SND_Spatialize(channel_t *ch, qboolean isstatic) { int i; + double f; 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) + if (ch->entnum > 0 && cls.state == ca_connected && cl_gameplayfix_soundsmovewithentities.integer) { - //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); + if (ch->entnum >= 32768) + { + //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]); + + if (ch->entnum > 32768) + if (!CL_VM_GetEntitySoundOrigin(ch->entnum, ch->origin)) + ch->entnum = 32768; // entity was removed, disown sound + } + else if (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]); + 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) + VectorMAM(0.5f, cl.entities[ch->entnum].render.mins, 0.5f, cl.entities[ch->entnum].render.maxs, ch->origin); + else + Matrix4x4_OriginFromMatrix(&cl.entities[ch->entnum].render.matrix, ch->origin); + } } mastervol = ch->master_vol; + // Adjust volume of static sounds if (isstatic) mastervol *= snd_staticvolume.value; + else if(!(ch->flags & CHANNELFLAG_FULLVOLUME)) // same as SND_PaintChannel uses + { + if(ch->entnum >= 32768) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_csqcchannel0volume.value; break; + case 1: mastervol *= snd_csqcchannel1volume.value; break; + case 2: mastervol *= snd_csqcchannel2volume.value; break; + case 3: mastervol *= snd_csqcchannel3volume.value; break; + case 4: mastervol *= snd_csqcchannel4volume.value; break; + case 5: mastervol *= snd_csqcchannel5volume.value; break; + case 6: mastervol *= snd_csqcchannel6volume.value; break; + case 7: mastervol *= snd_csqcchannel7volume.value; break; + default: break; + } + } + else if(ch->entnum == 0) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_worldchannel0volume.value; break; + case 1: mastervol *= snd_worldchannel1volume.value; break; + case 2: mastervol *= snd_worldchannel2volume.value; break; + case 3: mastervol *= snd_worldchannel3volume.value; break; + case 4: mastervol *= snd_worldchannel4volume.value; break; + case 5: mastervol *= snd_worldchannel5volume.value; break; + case 6: mastervol *= snd_worldchannel6volume.value; break; + case 7: mastervol *= snd_worldchannel7volume.value; break; + default: break; + } + } + else if(ch->entnum > 0 && ch->entnum <= cl.maxclients) + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_playerchannel0volume.value; break; + case 1: mastervol *= snd_playerchannel1volume.value; break; + case 2: mastervol *= snd_playerchannel2volume.value; break; + case 3: mastervol *= snd_playerchannel3volume.value; break; + case 4: mastervol *= snd_playerchannel4volume.value; break; + case 5: mastervol *= snd_playerchannel5volume.value; break; + case 6: mastervol *= snd_playerchannel6volume.value; break; + case 7: mastervol *= snd_playerchannel7volume.value; break; + default: break; + } + } + else + { + switch(ch->entchannel) + { + case 0: mastervol *= snd_entchannel0volume.value; break; + case 1: mastervol *= snd_entchannel1volume.value; break; + case 2: mastervol *= snd_entchannel2volume.value; break; + case 3: mastervol *= snd_entchannel3volume.value; break; + case 4: mastervol *= snd_entchannel4volume.value; break; + case 5: mastervol *= snd_entchannel5volume.value; break; + case 6: mastervol *= snd_entchannel6volume.value; break; + case 7: mastervol *= snd_entchannel7volume.value; break; + default: break; + } + } + } // anything coming from the view entity will always be full volume // LordHavoc: make sounds with ATTN_NONE have no spatialization @@ -1051,7 +1348,42 @@ void SND_Spatialize(channel_t *ch, qboolean isstatic) { Matrix4x4_Transform(&listener_matrix[i], ch->origin, source_vec); VectorNormalize(source_vec); + + switch(spatialmethod) + { + case SPATIAL_LOG: + if(dist == 0) + f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing + else + f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_POW: + f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; + f = spatialmin + spatialdiff * bound(0, f, 1); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_THRESH: + f = spatialmin + spatialdiff * (dist < spatialoffset); + VectorScale(source_vec, f, source_vec); + break; + case SPATIAL_NONE: + default: + break; + } + vol = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); + + if (snd_spatialization_occlusion.integer) + { + if (cl.worldmodel + && cl.worldmodel->brush.TraceLineOfSight + && !cl.worldmodel->brush.TraceLineOfSight(cl.worldmodel, listener_origin, ch->origin)) + { + vol *= 0.5f; + } + } + ch->listener_volume[i] = (int)bound(0, vol, 255); } } @@ -1068,27 +1400,46 @@ void SND_Spatialize(channel_t *ch, qboolean isstatic) void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, vec3_t origin, float fvol, float attenuation, qboolean isstatic) { + if (!sfx) + { + Con_Printf("S_PlaySfxOnChannel called with NULL??\n"); + return; + } // Initialize the channel + // a crash was reported on an in-use channel, so check here... + if (target_chan->sfx) + { + int channelindex = (int)(target_chan - channels); + Con_Printf("S_PlaySfxOnChannel(%s): channel %i already in use?? Clearing.\n", sfx->name, channelindex); + S_StopChannel (channelindex, true); + } + // We MUST set sfx LAST because otherwise we could crash a threaded mixer + // (otherwise we'd have to call SndSys_LockRenderBuffer here) memset (target_chan, 0, sizeof (*target_chan)); VectorCopy (origin, target_chan->origin); - target_chan->master_vol = (int)(fvol * 255); - target_chan->sfx = sfx; - target_chan->end = snd_renderbuffer->endframe + sfx->total_length; - target_chan->lastptime = snd_renderbuffer->endframe; target_chan->flags = flags; + target_chan->pos = 0; // start of the sound // If it's a static sound if (isstatic) { - if (sfx->loopstart == -1) + if (sfx->loopstart >= sfx->total_length) 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 S_LockSfx (sfx); + + // finally, set the sfx pointer, so the channel becomes valid for playback + // and will be noticed by the mixer + target_chan->sfx = sfx; + + // we have to set the channel volume AFTER the sfx because the function + // needs it for replaygain support + S_SetChannelVolume(target_chan - channels, fvol); } @@ -1096,7 +1447,6 @@ int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f { channel_t *target_chan, *check; int ch_idx; - int skip; if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) return -1; @@ -1104,9 +1454,6 @@ int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f 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) @@ -1127,13 +1474,8 @@ int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f continue; if (check->sfx == sfx && !check->pos) { - 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; - target_chan->end -= skip; + // use negative pos offset to delay this sound effect + target_chan->pos += (int)lhrandom(0, -0.1 * snd_renderbuffer->format.speed); break; } } @@ -1141,13 +1483,20 @@ int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float f return (target_chan - channels); } -void S_StopChannel (unsigned int channel_ind) +void S_StopChannel (unsigned int channel_ind, qboolean lockmutex) { channel_t *ch; if (channel_ind >= total_channels) return; + // we have to lock an audio mutex to prevent crashes if an audio mixer + // thread is currently mixing this channel + // the SndSys_LockRenderBuffer function uses such a mutex in + // threaded sound backends + if (lockmutex && !simsound) + SndSys_LockRenderBuffer(); + ch = &channels[channel_ind]; if (ch->sfx != NULL) { @@ -1157,15 +1506,17 @@ void S_StopChannel (unsigned int channel_ind) { snd_fetcher_endsb_t fetcher_endsb = sfx->fetcher->endsb; if (fetcher_endsb != NULL) - fetcher_endsb (ch); + fetcher_endsb (ch->fetcher_data); } // Remove the lock it holds S_UnlockSfx (sfx); + ch->fetcher_data = NULL; ch->sfx = NULL; } - ch->end = 0; + if (lockmutex && !simsound) + SndSys_UnlockRenderBuffer(); } @@ -1176,7 +1527,8 @@ qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean valu if (flag != CHANNELFLAG_FORCELOOP && flag != CHANNELFLAG_PAUSED && - flag != CHANNELFLAG_FULLVOLUME) + flag != CHANNELFLAG_FULLVOLUME && + flag != CHANNELFLAG_LOCALSOUND) return false; if (value) @@ -1194,11 +1546,12 @@ void S_StopSound(int entnum, int entchannel) for (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) if (channels[i].entnum == entnum && channels[i].entchannel == entchannel) { - S_StopChannel (i); + S_StopChannel (i, true); return; } } +extern void CDAudio_Stop(void); void S_StopAllSounds (void) { unsigned int i; @@ -1207,18 +1560,21 @@ void S_StopAllSounds (void) if (snd_renderbuffer == NULL) return; - 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)); + // stop CD audio because it may be using a faketrack + CDAudio_Stop(); - // Mute the contents of the submittion buffer if (simsound || SndSys_LockRenderBuffer ()) { int clear; size_t memsize; + for (i = 0; i < total_channels; i++) + S_StopChannel (i, false); + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + // Mute the contents of the submittion buffer 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); @@ -1244,9 +1600,35 @@ void S_PauseGameSounds (qboolean toggle) void S_SetChannelVolume (unsigned int ch_ind, float fvol) { + sfx_t *sfx = channels[ch_ind].sfx; + if(sfx->volume_peak > 0) + { + // Replaygain support + // Con_DPrintf("Setting volume on ReplayGain-enabled track... %f -> ", fvol); + fvol *= sfx->volume_mult; + if(fvol * sfx->volume_peak > 1) + fvol = 1 / sfx->volume_peak; + // Con_DPrintf("%f\n", fvol); + } channels[ch_ind].master_vol = (int)(fvol * 255.0f); } +float S_GetChannelPosition (unsigned int ch_ind) +{ + // note: this is NOT accurate yet + int s; + channel_t *ch = &channels[ch_ind]; + sfx_t *sfx = ch->sfx; + + s = ch->pos; + /* + if(!snd_usethreadedmixing) + s += _snd_mixahead.value * S_GetSoundRate(); + */ + return (s % sfx->total_length) / (float) S_GetSoundRate(); +} + + /* ================= @@ -1261,7 +1643,7 @@ void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) return; if (!sfx->fetcher) { - Con_DPrintf ("S_StaticSound: \"%s\" hasn't been precached\n", sfx->name); + Con_Printf ("S_StaticSound: \"%s\" hasn't been precached\n", sfx->name); return; } @@ -1308,17 +1690,20 @@ void S_UpdateAmbientSounds (void) // 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) + if (cl.time > cl.oldtime) { - 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 -= (int)(cl.realframetime * ambient_fade.value); if (chan->master_vol < vol) - chan->master_vol = vol; + { + chan->master_vol += (int)((cl.time - cl.oldtime) * ambient_fade.value); + if (chan->master_vol > vol) + chan->master_vol = vol; + } + else if (chan->master_vol > vol) + { + chan->master_vol -= (int)((cl.time - cl.oldtime) * ambient_fade.value); + if (chan->master_vol < vol) + chan->master_vol = vol; + } } for (i = 0;i < SND_LISTENERS;i++) @@ -1329,23 +1714,98 @@ void S_UpdateAmbientSounds (void) static void S_PaintAndSubmit (void) { unsigned int newsoundtime, paintedtime, endtime, maxtime, usedframes; + int usesoundtimehack; + static int soundtimehack = -1; + static int oldsoundtime = 0; - if (snd_renderbuffer == NULL || snd_blocked > 0 || nosound.integer) + if (snd_renderbuffer == NULL || nosound.integer) return; // Update sound time - if (cls.capturevideo_soundfile) // SUPER NASTY HACK to record non-realtime sound - newsoundtime = (unsigned int)((double)cls.capturevideo_frame * (double)snd_renderbuffer->format.speed / (double)cls.capturevideo_framerate); + snd_usethreadedmixing = false; + usesoundtimehack = true; + if (cls.timedemo) // SUPER NASTY HACK to mix non-realtime sound for more reliable benchmarking + { + usesoundtimehack = 1; + newsoundtime = (unsigned int)((double)cl.mtime[0] * (double)snd_renderbuffer->format.speed); + } + else if (cls.capturevideo.soundrate && !cls.capturevideo.realtime) // SUPER NASTY HACK to record non-realtime sound + { + usesoundtimehack = 2; + newsoundtime = (unsigned int)((double)cls.capturevideo.frame * (double)snd_renderbuffer->format.speed / (double)cls.capturevideo.framerate); + } else if (simsound) + { + usesoundtimehack = 3; newsoundtime = (unsigned int)((realtime - snd_starttime) * (double)snd_renderbuffer->format.speed); + } else + { + snd_usethreadedmixing = snd_threaded && !cls.capturevideo.soundrate; + usesoundtimehack = 0; newsoundtime = SndSys_GetSoundTime(); + } + // if the soundtimehack state changes we need to reset the soundtime + if (soundtimehack != usesoundtimehack) + { + snd_renderbuffer->startframe = snd_renderbuffer->endframe = soundtime = newsoundtime; + + // Mute the contents of the submission 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 (); + } + } + soundtimehack = usesoundtimehack; + + if (!soundtimehack && snd_blocked > 0) + return; + + if (snd_usethreadedmixing) + return; // the audio thread will mix its own data newsoundtime += extrasoundtime; if (newsoundtime < soundtime) - Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n", - 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 if (!soundtimehack) + Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n", + newsoundtime, soundtime); + } soundtime = newsoundtime; + recording_sound = (cls.capturevideo.soundrate != 0); + + // Lock submitbuffer + if (!simsound && !SndSys_LockRenderBuffer()) + { + // If the lock failed, stop here + Con_DPrint(">> S_PaintAndSubmit: SndSys_LockRenderBuffer() failed\n"); + return; + } // Check to make sure that we haven't overshot paintedtime = snd_renderbuffer->endframe; @@ -1353,17 +1813,46 @@ static void S_PaintAndSubmit (void) paintedtime = soundtime; // mix ahead of current position - endtime = soundtime + (unsigned int)(_snd_mixahead.value * (float)snd_renderbuffer->format.speed); + if (soundtimehack) + endtime = soundtime + (unsigned int)(_snd_mixahead.value * (float)snd_renderbuffer->format.speed); + else + endtime = soundtime + (unsigned int)(max(_snd_mixahead.value * (float)snd_renderbuffer->format.speed, min(3 * (soundtime - oldsoundtime), 0.3 * (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); + while (paintedtime < endtime) + { + unsigned int startoffset; + unsigned int nbframes; + + // see how much we can fit in the paint buffer + nbframes = endtime - paintedtime; + // limit to the end of the ring buffer (in case of wrapping) + startoffset = paintedtime % snd_renderbuffer->maxframes; + nbframes = min(nbframes, snd_renderbuffer->maxframes - startoffset); + + // mix into the buffer + S_MixToBuffer(&snd_renderbuffer->ring[startoffset * snd_renderbuffer->format.width * snd_renderbuffer->format.channels], nbframes); + + paintedtime += nbframes; + snd_renderbuffer->endframe = paintedtime; + } + if (!simsound) + SndSys_UnlockRenderBuffer(); + + // Remove outdated samples from the ring buffer, if any + if (snd_renderbuffer->startframe < soundtime) + snd_renderbuffer->startframe = soundtime; if (simsound) snd_renderbuffer->startframe = snd_renderbuffer->endframe; else SndSys_Submit(); + + oldsoundtime = soundtime; + + cls.soundstats.latency_milliseconds = (snd_renderbuffer->endframe - snd_renderbuffer->startframe) * 1000 / snd_renderbuffer->format.speed; } /* @@ -1375,20 +1864,57 @@ Called once each time through the main loop */ void S_Update(const matrix4x4_t *listenermatrix) { - unsigned int i, j, total; + unsigned int i, j, k; channel_t *ch, *combine; matrix4x4_t basematrix, rotatematrix; - if (snd_renderbuffer == NULL || snd_blocked > 0 || nosound.integer) + if (snd_renderbuffer == NULL || nosound.integer) return; - // If snd_swapstereo has changed, recompute the speaker layout - if (current_swapstereo != snd_swapstereo.integer) { - current_swapstereo = snd_swapstereo.integer; - S_SetSpeakerLayout(); + double mindist_trans, maxdist_trans; + + spatialmin = snd_spatialization_min.value; + spatialdiff = snd_spatialization_max.value - spatialmin; + + if(snd_spatialization_control.value) + { + spatialpower = snd_spatialization_power.value; + + if(spatialpower == 0) + { + spatialmethod = SPATIAL_LOG; + mindist_trans = log(max(1, snd_spatialization_min_radius.value)); + maxdist_trans = log(max(1, snd_spatialization_max_radius.value)); + } + else + { + spatialmethod = SPATIAL_POW; + mindist_trans = pow(snd_spatialization_min_radius.value, spatialpower); + maxdist_trans = pow(snd_spatialization_max_radius.value, spatialpower); + } + + if(mindist_trans - maxdist_trans == 0) + { + spatialmethod = SPATIAL_THRESH; + mindist_trans = snd_spatialization_min_radius.value; + } + else + { + spatialoffset = mindist_trans; + spatialfactor = 1 / (maxdist_trans - mindist_trans); + } + } + else + spatialmethod = SPATIAL_NONE; + } + // If snd_swapstereo or snd_channellayout has changed, recompute the channel layout + if (current_swapstereo != boolxor(snd_swapstereo.integer, v_flipped.integer) || + current_channellayout != snd_channellayout.integer) + S_SetChannelLayout(); + Matrix4x4_Invert_Simple(&basematrix, listenermatrix); Matrix4x4_OriginFromMatrix(listenermatrix, listener_origin); @@ -1415,11 +1941,14 @@ void S_Update(const matrix4x4_t *listenermatrix) combine = NULL; // update spatialization for static and dynamic sounds + cls.soundstats.totalsounds = 0; + cls.soundstats.mixedsounds = 0; ch = channels+NUM_AMBIENTS; for (i=NUM_AMBIENTS ; isfx) continue; + cls.soundstats.totalsounds++; // respatialize channel SND_Spatialize(ch, i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS); @@ -1457,29 +1986,18 @@ void S_Update(const matrix4x4_t *listenermatrix) } } } + for (k = 0;k < SND_LISTENERS;k++) + if (ch->listener_volume[k]) + break; + if (k < SND_LISTENERS) + cls.soundstats.mixedsounds++; } sound_spatialized = true; // debugging output if (snd_show.integer) - { - total = 0; - ch = channels; - for (i=0 ; isfx) - { - for (j = 0;j < SND_LISTENERS;j++) - if (ch->listener_volume[j]) - break; - if (j < SND_LISTENERS) - total++; - } - } - - Con_Printf("----(%u)----\n", total); - } + Con_Printf("----(%u)----\n", cls.soundstats.mixedsounds); S_PaintAndSubmit(); }