#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
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"};
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' : ' ',
{ 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]);
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)
{
(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;
}
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();
}
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"))
return;
snd_mempool = Mem_AllocPool("sound", 0, NULL);
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);
/*
==================
-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();
// Free it
if (sfx->fetcher != NULL && sfx->fetcher->free != NULL)
- sfx->fetcher->free (sfx);
+ sfx->fetcher->free (sfx->fetcher_data);
Mem_Free (sfx);
}
// 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;
if (first_to_die == -1)
return NULL;
- S_StopChannel (first_to_die);
-
return &channels[first_to_die];
}
Spatializes a channel
=================
*/
+extern cvar_t cl_gameplayfix_soundsmovewithentities;
void SND_Spatialize(channel_t *ch, qboolean isstatic)
{
int i;
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;
// 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);
}
// Lock the SFX during play
S_LockSfx (sfx);
+
+ // and finally, apply the volume
+ S_SetChannelVolume(target_chan - channels, fvol);
}
{
channel_t *target_chan, *check;
int ch_idx;
- int skip;
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)
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;
}
}
{
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;
}
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);
}
// 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++)
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)
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);
}
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();