]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - snd_main.c
vary r_drawparticles_drawdistance and r_drawdecals_drawdistance for
[xonotic/darkplaces.git] / snd_main.c
index a6945527c3837fb8b9b2924cf53b4640a485eb50..03b1358d161a79e2562dce42c4535b1cfecf5db3 100644 (file)
@@ -23,10 +23,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include "snd_main.h"
 #include "snd_ogg.h"
+#include "snd_modplug.h"
 
 
 #define SND_MIN_SPEED 8000
-#define SND_MAX_SPEED 48000
+#define SND_MAX_SPEED 96000
 #define SND_MIN_WIDTH 1
 #define SND_MAX_WIDTH 2
 #define SND_MIN_CHANNELS 1
@@ -167,13 +168,15 @@ cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (su
 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)"};
+cvar_t snd_soundradius = {0, "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)"};
 
 // 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"};
+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"};
 
 // Local cvars
 static cvar_t nosound = {0, "nosound", "0", "disables sound"};
@@ -268,7 +271,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' : ' ',
@@ -317,6 +320,7 @@ static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_spee
                { 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]);
@@ -400,7 +404,7 @@ static void S_SetChannelLayout (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)
                {
@@ -454,7 +458,7 @@ static void S_SetChannelLayout (void)
                                   (layout == SND_CHANNELLAYOUT_ALSA) ? "ALSA" : "standard");
        }
 
-       current_swapstereo = snd_swapstereo.integer;
+       current_swapstereo = boolxor(snd_swapstereo.integer, v_flipped.integer);
        current_channellayout = snd_channellayout.integer;
        current_channellayout_used = layout;
 }
@@ -686,6 +690,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();
 }
@@ -706,10 +718,16 @@ void S_Init(void)
        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);
 
@@ -724,7 +742,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_reload", S_Reload_f, "reload all sound files");
+       Cmd_AddCommand("snd_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files");
 
        Cvar_RegisterVariable(&nosound);
        Cvar_RegisterVariable(&snd_precache);
@@ -747,6 +765,7 @@ void S_Init(void)
        memset(channels, 0, MAX_CHANNELS * sizeof(channel_t));
 
        OGG_OpenLibrary ();
+       ModPlug_OpenLibrary ();
 }
 
 
@@ -760,6 +779,7 @@ Shutdown and free all resources
 void S_Terminate (void)
 {
        S_Shutdown ();
+       ModPlug_CloseLibrary ();
        OGG_CloseLibrary ();
 
        // Free all SFXs
@@ -773,13 +793,21 @@ void S_Terminate (void)
 
 /*
 ==================
-S_Reload_f
+S_UnloadAllSounds_f
 ==================
 */
-void S_Reload_f (void)
+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();
 
@@ -871,7 +899,7 @@ void S_FreeSfx (sfx_t *sfx, qboolean force)
 
        // Free it
        if (sfx->fetcher != NULL && sfx->fetcher->free != NULL)
-               sfx->fetcher->free (sfx);
+               sfx->fetcher->free (sfx->fetcher_data);
        Mem_Free (sfx);
 }
 
@@ -919,6 +947,9 @@ void S_ServerSounds (char serversound [][MAX_QPATH], unsigned int numsounds)
                sfx = S_FindName (serversound[i]);
                if (sfx != NULL)
                {
+                       // 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;
                }
@@ -949,9 +980,14 @@ sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean lock)
                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)
                S_LockSfx (sfx);
 
@@ -1035,41 +1071,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);
+                               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;
+                       break;
                }
 
-               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;
@@ -1080,8 +1117,6 @@ channel_t *SND_PickChannel(int entnum, int entchannel)
        if (first_to_die == -1)
                return NULL;
 
-       S_StopChannel (first_to_die);
-
        return &channels[first_to_die];
 }
 
@@ -1092,6 +1127,7 @@ SND_Spatialize
 Spatializes a channel
 =================
 */
+extern cvar_t cl_gameplayfix_soundsmovewithentities;
 void SND_Spatialize(channel_t *ch, qboolean isstatic)
 {
        int i;
@@ -1099,12 +1135,19 @@ void SND_Spatialize(channel_t *ch, qboolean isstatic)
        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)
+               {
+                       // TODO: sounds that follow CSQC entities?
+               }
+               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]);
+                       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;
@@ -1154,16 +1197,14 @@ 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 = (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 * snd_soundradius.value);
        }
@@ -1172,6 +1213,9 @@ void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags,
 
        // Lock the SFX during play
        S_LockSfx (sfx);
+
+       // and finally, apply the volume
+       S_SetChannelVolume(target_chan - channels, fvol);
 }
 
 
@@ -1179,7 +1223,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;
@@ -1187,9 +1230,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)
@@ -1210,13 +1250,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;
                }
        }
@@ -1240,15 +1275,15 @@ 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;
 }
 
 
@@ -1282,6 +1317,7 @@ void S_StopSound(int entnum, int entchannel)
                }
 }
 
+extern void CDAudio_Stop(void);
 void S_StopAllSounds (void)
 {
        unsigned int i;
@@ -1330,6 +1366,16 @@ 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);
 }
 
@@ -1394,17 +1440,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)
-               {
-                       chan->master_vol += (int)(cl.realframetime * ambient_fade.value);
-                       if (chan->master_vol > vol)
-                               chan->master_vol = vol;
-               }
-               else 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;
+                       {
+                               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++)
@@ -1415,20 +1464,48 @@ void S_UpdateAmbientSounds (void)
 static void S_PaintAndSubmit (void)
 {
        unsigned int newsoundtime, paintedtime, endtime, maxtime, usedframes;
+       qboolean usesoundtimehack;
+       static qboolean soundtimehack = true;
 
        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
+       usesoundtimehack = true;
+       if (cls.timedemo) // SUPER NASTY HACK to mix non-realtime sound for more reliable benchmarking
+               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
                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();
+               usesoundtimehack = false;
+       }
+       // 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;
 
        newsoundtime += extrasoundtime;
        if (newsoundtime < soundtime)
@@ -1450,7 +1527,7 @@ static void S_PaintAndSubmit (void)
                        Con_DPrintf("S_PaintAndSubmit: new extra sound time = %u\n",
                                                extrasoundtime);
                }
-               else
+               else if (!soundtimehack)
                        Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n",
                                           newsoundtime, soundtime);
        }
@@ -1492,11 +1569,8 @@ void S_Update(const matrix4x4_t *listenermatrix)
        if (snd_renderbuffer == NULL || nosound.integer)
                return;
 
-       if (snd_blocked > 0 && !(cls.capturevideo.soundrate && !cls.capturevideo.realtime))
-               return;
-
        // If snd_swapstereo or snd_channellayout has changed, recompute the channel layout
-       if (current_swapstereo != snd_swapstereo.integer ||
+       if (current_swapstereo != boolxor(snd_swapstereo.integer, v_flipped.integer) ||
                current_channellayout != snd_channellayout.integer)
                S_SetChannelLayout();