2019-12-08 12:28:52 +00:00
|
|
|
/*
|
|
|
|
** s_sound.cpp
|
|
|
|
** Main sound engine
|
2017-04-17 11:33:19 +00:00
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
2019-12-08 12:28:52 +00:00
|
|
|
** Copyright 1998-2016 Randy Heit
|
|
|
|
** Copyright 2002-2019 Christoph Oelckers
|
|
|
|
** All rights reserved.
|
2017-04-17 11:33:19 +00:00
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <io.h>
|
|
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
#include "i_system.h"
|
|
|
|
#include "i_sound.h"
|
|
|
|
#include "i_music.h"
|
|
|
|
#include "s_sound.h"
|
|
|
|
#include "s_sndseq.h"
|
|
|
|
#include "s_playlist.h"
|
|
|
|
#include "c_dispatch.h"
|
|
|
|
#include "m_random.h"
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "p_local.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "cmdlib.h"
|
|
|
|
#include "v_video.h"
|
|
|
|
#include "v_text.h"
|
|
|
|
#include "a_sharedglobal.h"
|
|
|
|
#include "gstrings.h"
|
|
|
|
#include "gi.h"
|
|
|
|
#include "po_man.h"
|
2016-09-20 21:13:12 +00:00
|
|
|
#include "serializer.h"
|
2016-03-01 15:47:10 +00:00
|
|
|
#include "d_player.h"
|
2017-01-08 17:45:30 +00:00
|
|
|
#include "g_levellocals.h"
|
2017-04-12 23:12:04 +00:00
|
|
|
#include "vm.h"
|
2019-10-01 18:04:46 +00:00
|
|
|
#include "s_music.h"
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
|
|
|
|
#define NORM_PITCH 128
|
|
|
|
#define NORM_PRIORITY 64
|
|
|
|
#define NORM_SEP 0
|
|
|
|
|
|
|
|
#define S_PITCH_PERTURB 1
|
|
|
|
#define S_STEREO_SWING 0.75
|
|
|
|
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
|
|
|
|
extern float S_GetMusicVolume (const char *music);
|
|
|
|
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
2019-08-23 19:57:56 +00:00
|
|
|
void I_CloseSound();
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
|
2017-04-26 18:51:06 +00:00
|
|
|
static void S_LoadSound3D(sfxinfo_t *sfx, FSoundLoadBuffer *pBuffer);
|
2019-12-08 12:28:52 +00:00
|
|
|
static bool S_CheckSoundLimit(sfxinfo_t *sfx, const FVector3 &pos, int near_limit, float limit_range, int sourcetype, const void *actor, int channel);
|
|
|
|
static bool S_IsChannelUsed(int sourcetype, const void *actor, int channel, int *seen);
|
2016-03-01 15:47:10 +00:00
|
|
|
static void S_ActivatePlayList(bool goBack);
|
2019-12-08 12:28:52 +00:00
|
|
|
void CalcPosVel(FSoundChan *chan, FVector3 *pos, FVector3 *vel);
|
|
|
|
static void CalcPosVel(int type, const void *source,
|
2016-03-01 15:47:10 +00:00
|
|
|
const float pt[3], int channel, int chanflags, FVector3 *pos, FVector3 *vel);
|
2016-03-31 19:13:32 +00:00
|
|
|
static void CalcSectorSoundOrg(const DVector3 &listenpos, const sector_t *sec, int channum, FVector3 &res);
|
|
|
|
static void CalcPolyobjSoundOrg(const DVector3 &listenpos, const FPolyObj *poly, FVector3 &res);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
bool SoundPaused; // whether sound is paused
|
|
|
|
int RestartEvictionsAt; // do not restart evicted channels before this time
|
|
|
|
SoundListener listener;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
|
|
|
|
int sfx_empty;
|
|
|
|
|
|
|
|
FSoundChan *Channels;
|
|
|
|
FSoundChan *FreeChannels;
|
|
|
|
|
|
|
|
FRolloffInfo S_Rolloff;
|
2018-11-30 16:07:42 +00:00
|
|
|
TArray<uint8_t> S_SoundCurve;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
FBoolCVar noisedebug ("noise", false, 0); // [RH] Print sound debugging info?
|
2017-03-16 09:37:55 +00:00
|
|
|
CUSTOM_CVAR (Int, snd_channels, 128, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // number of channels available
|
|
|
|
{
|
|
|
|
if (self < 64) self = 64;
|
|
|
|
}
|
2017-04-16 23:06:54 +00:00
|
|
|
CVAR(Bool, snd_waterreverb, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_NoiseDebug
|
|
|
|
//
|
|
|
|
// [RH] Print sound debug info. Called by status bar.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_NoiseDebug (void)
|
|
|
|
{
|
|
|
|
FSoundChan *chan;
|
|
|
|
FVector3 listener;
|
|
|
|
FVector3 origin;
|
|
|
|
int y, color;
|
|
|
|
|
|
|
|
y = 32 * CleanYfac;
|
|
|
|
screen->DrawText (SmallFont, CR_YELLOW, 0, y, "*** SOUND DEBUG INFO ***", TAG_DONE);
|
|
|
|
y += 8;
|
|
|
|
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 0, y, "name", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 70, y, "x", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 120, y, "y", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 170, y, "z", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 220, y, "vol", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 260, y, "dist", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 300, y, "chan", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 340, y, "pri", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 380, y, "flags", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 460, y, "aud", TAG_DONE);
|
|
|
|
screen->DrawText (SmallFont, CR_GOLD, 520, y, "pos", TAG_DONE);
|
|
|
|
y += 8;
|
|
|
|
|
|
|
|
if (Channels == NULL)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-03-25 17:19:54 +00:00
|
|
|
listener = players[consoleplayer].camera->SoundPos();
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// Display the oldest channel first.
|
|
|
|
for (chan = Channels; chan->NextChan != NULL; chan = chan->NextChan)
|
|
|
|
{ }
|
|
|
|
while (y < SCREENHEIGHT - 16)
|
|
|
|
{
|
|
|
|
char temp[32];
|
|
|
|
|
|
|
|
CalcPosVel(chan, &origin, NULL);
|
|
|
|
color = (chan->ChanFlags & CHAN_LOOP) ? CR_BROWN : CR_GREY;
|
|
|
|
|
|
|
|
// Name
|
|
|
|
Wads.GetLumpName (temp, S_sfx[chan->SoundID].lumpnum);
|
|
|
|
temp[8] = 0;
|
|
|
|
screen->DrawText (SmallFont, color, 0, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
if (!(chan->ChanFlags & CHAN_IS3D))
|
|
|
|
{
|
|
|
|
screen->DrawText(SmallFont, color, 70, y, "---", TAG_DONE); // X
|
|
|
|
screen->DrawText(SmallFont, color, 120, y, "---", TAG_DONE); // Y
|
|
|
|
screen->DrawText(SmallFont, color, 170, y, "---", TAG_DONE); // Z
|
|
|
|
screen->DrawText(SmallFont, color, 260, y, "---", TAG_DONE); // Distance
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// X coordinate
|
|
|
|
mysnprintf(temp, countof(temp), "%.0f", origin.X);
|
|
|
|
screen->DrawText(SmallFont, color, 70, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Y coordinate
|
|
|
|
mysnprintf(temp, countof(temp), "%.0f", origin.Z);
|
|
|
|
screen->DrawText(SmallFont, color, 120, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Z coordinate
|
|
|
|
mysnprintf(temp, countof(temp), "%.0f", origin.Y);
|
|
|
|
screen->DrawText(SmallFont, color, 170, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Distance
|
|
|
|
if (chan->DistanceScale > 0)
|
|
|
|
{
|
|
|
|
mysnprintf(temp, countof(temp), "%.0f", (origin - listener).Length());
|
|
|
|
screen->DrawText(SmallFont, color, 260, y, temp, TAG_DONE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
screen->DrawText(SmallFont, color, 260, y, "---", TAG_DONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Volume
|
|
|
|
mysnprintf(temp, countof(temp), "%.2g", chan->Volume);
|
|
|
|
screen->DrawText(SmallFont, color, 220, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Channel
|
|
|
|
mysnprintf(temp, countof(temp), "%d", chan->EntChannel);
|
|
|
|
screen->DrawText(SmallFont, color, 300, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Priority
|
|
|
|
mysnprintf(temp, countof(temp), "%d", chan->Priority);
|
|
|
|
screen->DrawText(SmallFont, color, 340, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Flags
|
|
|
|
mysnprintf(temp, countof(temp), "%s3%sZ%sU%sM%sN%sA%sL%sE%sV",
|
|
|
|
(chan->ChanFlags & CHAN_IS3D) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_LISTENERZ) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_UI) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_MAYBE_LOCAL) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_NOPAUSE) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_AREA) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_LOOP) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_EVICTED) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK,
|
|
|
|
(chan->ChanFlags & CHAN_VIRTUAL) ? TEXTCOLOR_GREEN : TEXTCOLOR_BLACK);
|
|
|
|
screen->DrawText(SmallFont, color, 380, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Audibility
|
|
|
|
mysnprintf(temp, countof(temp), "%.4f", GSnd->GetAudibility(chan));
|
|
|
|
screen->DrawText(SmallFont, color, 460, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
// Position
|
|
|
|
mysnprintf(temp, countof(temp), "%u", GSnd->GetPosition(chan));
|
|
|
|
screen->DrawText(SmallFont, color, 520, y, temp, TAG_DONE);
|
|
|
|
|
|
|
|
|
|
|
|
y += 8;
|
|
|
|
if (chan->PrevChan == &Channels)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
chan = (FSoundChan *)((size_t)chan->PrevChan - myoffsetof(FSoundChan, NextChan));
|
|
|
|
}
|
|
|
|
V_SetBorderNeedRefresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
static FString LastLocalSndInfo;
|
|
|
|
static FString LastLocalSndSeq;
|
|
|
|
void S_AddLocalSndInfo(int lump);
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Init
|
|
|
|
//
|
|
|
|
// Initializes sound stuff, including volume. Sets channels, SFX and
|
|
|
|
// music volume, allocates channel buffer, and sets S_sfx lookup.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Init ()
|
|
|
|
{
|
|
|
|
int curvelump;
|
|
|
|
|
|
|
|
// Heretic and Hexen have sound curve lookup tables. Doom does not.
|
2019-10-01 18:04:46 +00:00
|
|
|
I_InitSound();
|
2016-03-01 15:47:10 +00:00
|
|
|
curvelump = Wads.CheckNumForName ("SNDCURVE");
|
|
|
|
if (curvelump >= 0)
|
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
S_SoundCurve.Resize(Wads.LumpLength (curvelump));
|
|
|
|
Wads.ReadLump(curvelump, S_SoundCurve.Data());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
S_SoundCurve.Clear();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Free all channels for use.
|
|
|
|
while (Channels != NULL)
|
|
|
|
{
|
|
|
|
S_ReturnChannel(Channels);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_InitData
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_InitData ()
|
|
|
|
{
|
|
|
|
LastLocalSndInfo = LastLocalSndSeq = "";
|
|
|
|
S_ParseSndInfo (false);
|
|
|
|
S_ParseSndSeq (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Shutdown
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Shutdown ()
|
|
|
|
{
|
|
|
|
FSoundChan *chan, *next;
|
|
|
|
|
2019-10-01 18:04:46 +00:00
|
|
|
S_StopMusic(true);
|
|
|
|
mus_playing.LastSong = ""; // If this isn't reset here, the song would attempt resume at the most inpopportune time...
|
|
|
|
S_StopAllChannels();
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2019-10-09 12:05:20 +00:00
|
|
|
if (GSnd)
|
|
|
|
GSnd->UpdateSounds();
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
for (chan = FreeChannels; chan != NULL; chan = next)
|
|
|
|
{
|
|
|
|
next = chan->NextChan;
|
|
|
|
delete chan;
|
|
|
|
}
|
|
|
|
FreeChannels = NULL;
|
2019-10-01 18:04:46 +00:00
|
|
|
|
|
|
|
if (GSnd != NULL)
|
|
|
|
{
|
|
|
|
I_CloseSound();
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
2019-10-01 18:04:46 +00:00
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Start
|
|
|
|
//
|
|
|
|
// Per level startup code. Kills playing sounds at start of level
|
|
|
|
// and starts new music.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Start ()
|
|
|
|
{
|
|
|
|
if (GSnd)
|
|
|
|
{
|
|
|
|
// kill all playing sounds at start of level (trust me - a good idea)
|
|
|
|
S_StopAllChannels();
|
|
|
|
|
|
|
|
// Check for local sound definitions. Only reload if they differ
|
|
|
|
// from the previous ones.
|
|
|
|
FString LocalSndInfo;
|
|
|
|
FString LocalSndSeq;
|
2019-08-23 15:15:19 +00:00
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
// To be certain better check whether level is valid!
|
|
|
|
if (level.info)
|
|
|
|
{
|
|
|
|
LocalSndInfo = level.info->SoundInfo;
|
|
|
|
LocalSndSeq = level.info->SndSeq;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool parse_ss = false;
|
|
|
|
|
|
|
|
// This level uses a different local SNDINFO
|
|
|
|
if (LastLocalSndInfo.CompareNoCase(LocalSndInfo) != 0 || !level.info)
|
|
|
|
{
|
|
|
|
// First delete the old sound list
|
2019-08-23 15:15:19 +00:00
|
|
|
for(unsigned i = 1; i < S_sfx.Size(); i++)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
S_UnloadSound(&S_sfx[i]);
|
|
|
|
}
|
2019-08-23 15:15:19 +00:00
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
// Parse the global SNDINFO
|
|
|
|
S_ParseSndInfo(true);
|
2019-08-23 15:15:19 +00:00
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
if (LocalSndInfo.IsNotEmpty())
|
|
|
|
{
|
|
|
|
// Now parse the local SNDINFO
|
|
|
|
int j = Wads.CheckNumForFullName(LocalSndInfo, true);
|
|
|
|
if (j>=0) S_AddLocalSndInfo(j);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also reload the SNDSEQ if the SNDINFO was replaced!
|
|
|
|
parse_ss = true;
|
|
|
|
}
|
|
|
|
else if (LastLocalSndSeq.CompareNoCase(LocalSndSeq) != 0)
|
|
|
|
{
|
|
|
|
parse_ss = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parse_ss)
|
|
|
|
{
|
|
|
|
S_ParseSndSeq(LocalSndSeq.IsNotEmpty()? Wads.CheckNumForFullName(LocalSndSeq, true) : -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
LastLocalSndInfo = LocalSndInfo;
|
|
|
|
LastLocalSndSeq = LocalSndSeq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_PrecacheLevel
|
|
|
|
//
|
|
|
|
// Like R_PrecacheLevel, but for sounds.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_PrecacheLevel ()
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
if (GSnd)
|
|
|
|
{
|
|
|
|
for (i = 0; i < S_sfx.Size(); ++i)
|
|
|
|
{
|
|
|
|
S_sfx[i].bUsed = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
AActor *actor;
|
|
|
|
TThinkerIterator<AActor> iterator;
|
|
|
|
|
|
|
|
// Precache all sounds known to be used by the currently spawned actors.
|
|
|
|
while ( (actor = iterator.Next()) != NULL )
|
|
|
|
{
|
2017-01-14 13:35:11 +00:00
|
|
|
IFVIRTUALPTR(actor, AActor, MarkPrecacheSounds)
|
|
|
|
{
|
|
|
|
VMValue params[1] = { actor };
|
2017-04-12 23:12:04 +00:00
|
|
|
VMCall(func, params, 1, nullptr, 0);
|
2017-01-14 13:35:11 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2017-03-09 22:30:42 +00:00
|
|
|
for (auto snd : gameinfo.PrecachedSounds)
|
2017-01-10 00:00:06 +00:00
|
|
|
{
|
2017-03-09 22:30:42 +00:00
|
|
|
FSoundID(snd).MarkUsed();
|
2017-01-10 00:00:06 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
// Precache all extra sounds requested by this map.
|
2017-03-09 22:30:42 +00:00
|
|
|
for (auto snd : level.info->PrecacheSounds)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2017-03-09 22:30:42 +00:00
|
|
|
FSoundID(snd).MarkUsed();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
// Don't unload sounds that are playing right now.
|
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
|
|
|
chan->SoundID.MarkUsed();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 1; i < S_sfx.Size(); ++i)
|
|
|
|
{
|
|
|
|
if (S_sfx[i].bUsed)
|
|
|
|
{
|
|
|
|
S_CacheSound (&S_sfx[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = 1; i < S_sfx.Size(); ++i)
|
|
|
|
{
|
|
|
|
if (!S_sfx[i].bUsed && S_sfx[i].link == sfxinfo_t::NO_LINK)
|
|
|
|
{
|
|
|
|
S_UnloadSound (&S_sfx[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_CacheSound
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_CacheSound (sfxinfo_t *sfx)
|
|
|
|
{
|
|
|
|
if (GSnd)
|
|
|
|
{
|
|
|
|
if (sfx->bPlayerReserve)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2016-09-30 08:50:41 +00:00
|
|
|
sfxinfo_t *orig = sfx;
|
|
|
|
while (!sfx->bRandomHeader && sfx->link != sfxinfo_t::NO_LINK)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-09-30 08:50:41 +00:00
|
|
|
sfx = &S_sfx[sfx->link];
|
|
|
|
}
|
|
|
|
if (sfx->bRandomHeader)
|
|
|
|
{
|
|
|
|
S_CacheRandomSound(sfx);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-04-26 18:51:06 +00:00
|
|
|
// Since we do not know in what format the sound will be used, we have to cache both.
|
|
|
|
FSoundLoadBuffer SoundBuffer;
|
|
|
|
S_LoadSound(sfx, &SoundBuffer);
|
|
|
|
S_LoadSound3D(sfx, &SoundBuffer);
|
2016-03-01 15:47:10 +00:00
|
|
|
sfx->bUsed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_UnloadSound
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_UnloadSound (sfxinfo_t *sfx)
|
|
|
|
{
|
2017-05-08 23:59:31 +00:00
|
|
|
if (sfx->data3d.isValid() && sfx->data != sfx->data3d)
|
|
|
|
GSnd->UnloadSound(sfx->data3d);
|
2016-03-01 15:47:10 +00:00
|
|
|
if (sfx->data.isValid())
|
|
|
|
GSnd->UnloadSound(sfx->data);
|
2017-05-08 23:59:31 +00:00
|
|
|
if (sfx->data.isValid() || sfx->data3d.isValid())
|
2016-08-28 07:55:04 +00:00
|
|
|
DPrintf(DMSG_NOTIFY, "Unloaded sound \"%s\" (%td)\n", sfx->name.GetChars(), sfx - &S_sfx[0]);
|
2017-05-08 23:59:31 +00:00
|
|
|
sfx->data.Clear();
|
|
|
|
sfx->data3d.Clear();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_GetChannel
|
|
|
|
//
|
|
|
|
// Returns a free channel for the system sound interface.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FISoundChannel *S_GetChannel(void *syschan)
|
|
|
|
{
|
|
|
|
FSoundChan *chan;
|
|
|
|
|
|
|
|
if (FreeChannels != NULL)
|
|
|
|
{
|
|
|
|
chan = FreeChannels;
|
|
|
|
S_UnlinkChannel(chan);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chan = new FSoundChan;
|
|
|
|
memset(chan, 0, sizeof(*chan));
|
|
|
|
}
|
|
|
|
S_LinkChannel(chan, &Channels);
|
|
|
|
chan->SysChannel = syschan;
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ReturnChannel
|
|
|
|
//
|
|
|
|
// Returns a channel to the free pool.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_ReturnChannel(FSoundChan *chan)
|
|
|
|
{
|
|
|
|
S_UnlinkChannel(chan);
|
|
|
|
memset(chan, 0, sizeof(*chan));
|
|
|
|
S_LinkChannel(chan, &FreeChannels);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_UnlinkChannel
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_UnlinkChannel(FSoundChan *chan)
|
|
|
|
{
|
|
|
|
*(chan->PrevChan) = chan->NextChan;
|
|
|
|
if (chan->NextChan != NULL)
|
|
|
|
{
|
|
|
|
chan->NextChan->PrevChan = chan->PrevChan;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_LinkChannel
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_LinkChannel(FSoundChan *chan, FSoundChan **head)
|
|
|
|
{
|
|
|
|
chan->NextChan = *head;
|
|
|
|
if (chan->NextChan != NULL)
|
|
|
|
{
|
|
|
|
chan->NextChan->PrevChan = &chan->NextChan;
|
|
|
|
}
|
|
|
|
*head = chan;
|
|
|
|
chan->PrevChan = head;
|
|
|
|
}
|
|
|
|
|
|
|
|
// [RH] Split S_StartSoundAtVolume into multiple parts so that sounds can
|
|
|
|
// be specified both by id and by name. Also borrowed some stuff from
|
|
|
|
// Hexen and parameters from Quake.
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CalcPosVel
|
|
|
|
//
|
|
|
|
// Retrieves a sound's position and velocity for 3D sounds. This version
|
|
|
|
// is for an already playing sound.
|
|
|
|
//
|
|
|
|
//=========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void CalcPosVel(FSoundChan *chan, FVector3 *pos, FVector3 *vel)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
CalcPosVel(chan->SourceType, chan->Source, chan->Point,
|
2016-03-01 15:47:10 +00:00
|
|
|
chan->EntChannel, chan->ChanFlags, pos, vel);
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================
|
|
|
|
//
|
|
|
|
// CalcPosVel
|
|
|
|
//
|
|
|
|
// This version is for sounds that haven't started yet so have no channel.
|
|
|
|
//
|
|
|
|
//=========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
static void CalcPosVel(int type, const void *source,
|
|
|
|
const float pt[3], int channum, int chanflags, FVector3 *pos, FVector3 *vel)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (pos != NULL)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
DVector3 listenpos;
|
|
|
|
int pgroup;
|
|
|
|
AActor *listener = players[consoleplayer].camera;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2016-03-31 19:13:32 +00:00
|
|
|
if (listener != NULL)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
listenpos = listener->Pos();
|
2016-04-04 10:46:32 +00:00
|
|
|
*pos = listener->SoundPos();
|
2016-03-31 19:13:32 +00:00
|
|
|
pgroup = listener->Sector->PortalGroup;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
listenpos.Zero();
|
2017-02-12 09:26:25 +00:00
|
|
|
pos->Zero();
|
2016-03-31 19:13:32 +00:00
|
|
|
pgroup = 0;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2019-12-08 12:28:52 +00:00
|
|
|
if (vel) vel->Zero();
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// [BL] Moved this case out of the switch statement to make code easier
|
|
|
|
// on static analysis.
|
|
|
|
if(type == SOURCE_Unattached)
|
2016-03-31 19:13:32 +00:00
|
|
|
{
|
|
|
|
sector_t *sec = P_PointInSector(pt[0], pt[2]);
|
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, sec->PortalGroup);
|
2016-06-01 09:14:25 +00:00
|
|
|
pos->X = pt[0] - (float)disp.X;
|
2016-03-31 19:13:32 +00:00
|
|
|
pos->Y = !(chanflags & CHAN_LISTENERZ) ? pt[1] : (float)listenpos.Z;
|
2016-06-01 09:14:25 +00:00
|
|
|
pos->Z = pt[2] - (float)disp.Y;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case SOURCE_None:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SOURCE_Actor:
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
auto actor = (AActor*)source;
|
2016-03-01 15:47:10 +00:00
|
|
|
//assert(actor != NULL);
|
|
|
|
if (actor != NULL)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, actor->Sector->PortalGroup);
|
2016-06-01 09:14:25 +00:00
|
|
|
DVector3 posi = actor->Pos() - disp;
|
2016-03-31 19:13:32 +00:00
|
|
|
*pos = { (float)posi.X, (float)posi.Z, (float)posi.Y };
|
2019-12-08 12:28:52 +00:00
|
|
|
if (vel)
|
|
|
|
{
|
|
|
|
vel->X = float(actor->Vel.X * TICRATE);
|
|
|
|
vel->Y = float(actor->Vel.Z * TICRATE);
|
|
|
|
vel->Z = float(actor->Vel.Y * TICRATE);
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
break;
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
case SOURCE_Sector:
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
auto sector = (sector_t*)source;
|
2016-03-01 15:47:10 +00:00
|
|
|
assert(sector != NULL);
|
|
|
|
if (sector != NULL)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, sector->PortalGroup);
|
2016-03-01 15:47:10 +00:00
|
|
|
if (chanflags & CHAN_AREA)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
// listener must be reversely offset to calculate the proper sound origin.
|
2016-06-01 09:14:25 +00:00
|
|
|
CalcSectorSoundOrg(listenpos + disp, sector, channum, *pos);
|
2018-06-04 17:10:58 +00:00
|
|
|
pos->X -= (float)disp.X;
|
|
|
|
pos->Z -= (float)disp.Y;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
|
2016-06-01 09:14:25 +00:00
|
|
|
pos->X = (float)(sector->centerspot.X - disp.X);
|
|
|
|
pos->Z = (float)(sector->centerspot.Y - disp.Y);
|
2016-03-01 15:47:10 +00:00
|
|
|
chanflags |= CHAN_LISTENERZ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
case SOURCE_Polyobj:
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
auto poly = (FPolyObj*)source;
|
2016-03-01 15:47:10 +00:00
|
|
|
assert(poly != NULL);
|
2016-03-31 19:13:32 +00:00
|
|
|
if (poly != NULL)
|
|
|
|
{
|
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, poly->CenterSubsector->sector->PortalGroup);
|
2016-06-01 09:14:25 +00:00
|
|
|
CalcPolyobjSoundOrg(listenpos + disp, poly, *pos);
|
2018-08-25 22:23:49 +00:00
|
|
|
pos->X -= (float)disp.X;
|
|
|
|
pos->Z -= (float)disp.Y;
|
2016-03-31 19:13:32 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
if ((chanflags & CHAN_LISTENERZ) && players[consoleplayer].camera != NULL)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
pos->Y = (float)listenpos.Z;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-03 10:36:52 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ValidatePosVel
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2018-03-29 14:36:48 +00:00
|
|
|
inline bool Validate(const float value, const float limit)
|
2018-03-03 10:36:52 +00:00
|
|
|
{
|
2018-03-29 14:36:48 +00:00
|
|
|
return value >= -limit && value <= limit;
|
2018-03-03 10:36:52 +00:00
|
|
|
}
|
|
|
|
|
2018-03-29 14:36:48 +00:00
|
|
|
static bool Validate(const FVector3 &value, const float limit, const char *const name, const AActor *const actor)
|
2018-03-03 10:36:52 +00:00
|
|
|
{
|
2018-03-29 14:36:48 +00:00
|
|
|
const bool valid =
|
|
|
|
Validate(value.X, limit)
|
|
|
|
&& Validate(value.Y, limit)
|
|
|
|
&& Validate(value.Z, limit);
|
2018-03-03 10:36:52 +00:00
|
|
|
|
|
|
|
if (!valid)
|
|
|
|
{
|
2018-05-13 07:13:59 +00:00
|
|
|
// Sound position and velocity have Y and Z axes swapped comparing to map coordinate system
|
|
|
|
Printf(TEXTCOLOR_RED "Invalid sound %s " TEXTCOLOR_WHITE "(%f, %f, %f)", name, value.X, value.Z, value.Y);
|
2018-03-03 10:36:52 +00:00
|
|
|
|
|
|
|
if (actor == nullptr)
|
|
|
|
{
|
|
|
|
Printf("\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf(TEXTCOLOR_RED " for actor of class " TEXTCOLOR_WHITE "%s\n", actor->GetClass()->TypeName.GetChars());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool ValidatePosVel(const AActor *actor, const FVector3 &pos, const FVector3 &vel)
|
|
|
|
{
|
2018-03-29 14:36:48 +00:00
|
|
|
// The actual limit for map coordinates
|
2019-01-08 14:20:14 +00:00
|
|
|
static const float POSITION_LIMIT = 1024.f * 1024.f;
|
2018-03-29 14:36:48 +00:00
|
|
|
const bool valid = Validate(pos, POSITION_LIMIT, "position", actor);
|
|
|
|
|
|
|
|
// The maximum velocity is enough to travel through entire map in one tic
|
2018-03-30 07:42:22 +00:00
|
|
|
static const float VELOCITY_LIMIT = 2 * POSITION_LIMIT * TICRATE;
|
2018-03-29 14:36:48 +00:00
|
|
|
return Validate(vel, VELOCITY_LIMIT, "velocity", actor) && valid;
|
2018-03-03 10:36:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool ValidatePosVel(const FSoundChan *const chan, const FVector3 &pos, const FVector3 &vel)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
return ValidatePosVel(chan->SourceType == SOURCE_Actor ? (AActor*)chan->Source : nullptr, pos, vel);
|
2018-03-03 10:36:52 +00:00
|
|
|
}
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CalcSectorSoundOrg
|
|
|
|
//
|
|
|
|
// Returns the perceived sound origin for a sector. If the listener is
|
|
|
|
// inside the sector, then the origin is their location. Otherwise, the
|
|
|
|
// origin is from the nearest wall on the sector.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-03-31 19:13:32 +00:00
|
|
|
static void CalcSectorSoundOrg(const DVector3 &listenpos, const sector_t *sec, int channum, FVector3 &pos)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (!(i_compatflags & COMPATF_SECTORSOUNDS))
|
|
|
|
{
|
|
|
|
// Are we inside the sector? If yes, the closest point is the one we're on.
|
2016-04-11 19:02:44 +00:00
|
|
|
if (P_PointInSector(listenpos.X, listenpos.Y) == sec)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
pos.X = (float)listenpos.X;
|
|
|
|
pos.Z = (float)listenpos.Y;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Find the closest point on the sector's boundary lines and use
|
|
|
|
// that as the perceived origin of the sound.
|
2016-03-31 19:13:32 +00:00
|
|
|
DVector2 xy;
|
|
|
|
sec->ClosestPoint(listenpos, xy);
|
|
|
|
pos.X = (float)xy.X;
|
|
|
|
pos.Z = (float)xy.Y;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
pos.X = float(sec->centerspot.X);
|
|
|
|
pos.Z = float(sec->centerspot.Y);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set sound vertical position based on channel.
|
|
|
|
if (channum == CHAN_FLOOR)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
pos.Y = (float)MIN<double>(sec->floorplane.ZatPoint(listenpos), listenpos.Z);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else if (channum == CHAN_CEILING)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
pos.Y = (float)MAX<double>(sec->ceilingplane.ZatPoint(listenpos), listenpos.Z);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else if (channum == CHAN_INTERIOR)
|
|
|
|
{
|
2016-03-31 19:13:32 +00:00
|
|
|
pos.Y = (float)clamp<double>(listenpos.Z, sec->floorplane.ZatPoint(listenpos), sec->ceilingplane.ZatPoint(listenpos));
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CalcPolySoundOrg
|
|
|
|
//
|
|
|
|
// Returns the perceived sound origin for a polyobject. This is similar to
|
|
|
|
// CalcSectorSoundOrg, except there is no special case for being "inside"
|
|
|
|
// a polyobject, so the sound literally comes from the polyobject's walls.
|
|
|
|
// Vertical position of the sound always comes from the visible wall.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2016-03-31 19:13:32 +00:00
|
|
|
static void CalcPolyobjSoundOrg(const DVector3 &listenpos, const FPolyObj *poly, FVector3 &pos)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
side_t *side;
|
|
|
|
sector_t *sec;
|
|
|
|
|
2016-03-31 19:13:32 +00:00
|
|
|
DVector2 ppos;
|
|
|
|
poly->ClosestPoint(listenpos, ppos, &side);
|
|
|
|
pos.X = (float)ppos.X;
|
|
|
|
pos.Z = (float)ppos.Y;
|
2016-03-01 15:47:10 +00:00
|
|
|
sec = side->sector;
|
2016-03-31 19:13:32 +00:00
|
|
|
pos.Y = (float)clamp<double>(listenpos.Z, sec->floorplane.ZatPoint(listenpos), sec->ceilingplane.ZatPoint(listenpos));
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StartSound
|
|
|
|
//
|
|
|
|
// 0 attenuation means full volume over whole level.
|
|
|
|
// 0 < attenuation means to scale the distance by that amount when
|
|
|
|
// calculating volume.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
FSoundChan *S_StartSound(int type, const void *source,
|
2016-03-01 15:47:10 +00:00
|
|
|
const FVector3 *pt, int channel, FSoundID sound_id, float volume, float attenuation,
|
2019-12-08 12:28:52 +00:00
|
|
|
FRolloffInfo *forcedrolloff, float spitch)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
sfxinfo_t *sfx;
|
|
|
|
int chanflags;
|
|
|
|
int basepriority;
|
|
|
|
int org_id;
|
|
|
|
int pitch;
|
|
|
|
FSoundChan *chan;
|
|
|
|
FVector3 pos, vel;
|
|
|
|
FRolloffInfo *rolloff;
|
2017-04-26 18:51:06 +00:00
|
|
|
FSoundLoadBuffer SoundBuffer;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
if (sound_id <= 0 || volume <= 0 || nosfx || nosound )
|
|
|
|
return NULL;
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
if (type == SOURCE_Unattached && pt == nullptr) type = SOURCE_None;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
org_id = sound_id;
|
|
|
|
chanflags = channel & ~7;
|
|
|
|
channel &= 7;
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
CalcPosVel(type, source, &pt->X, channel, chanflags, &pos, &vel);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
if (!ValidatePosVel(type == SOURCE_Actor ? (AActor*)source : nullptr, pos, vel))
|
2018-03-03 10:36:52 +00:00
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
if (i_compatflags & COMPATF_MAGICSILENCE)
|
|
|
|
{ // For people who just can't play without a silent BFG.
|
|
|
|
channel = CHAN_WEAPON;
|
|
|
|
}
|
|
|
|
else if ((chanflags & CHAN_MAYBE_LOCAL) && (i_compatflags & COMPATF_SILENTPICKUP))
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (source != nullptr && source == listener.ListenerObject)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sfx = &S_sfx[sound_id];
|
|
|
|
|
|
|
|
// Scale volume according to SNDINFO data.
|
|
|
|
volume = MIN(volume * sfx->Volume, 1.f);
|
|
|
|
if (volume <= 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
// When resolving a link we do not want to get the NearLimit of
|
|
|
|
// the referenced sound so some additional checks are required
|
|
|
|
int near_limit = sfx->NearLimit;
|
|
|
|
float limit_range = sfx->LimitRange;
|
2016-12-26 20:07:21 +00:00
|
|
|
auto pitchmask = sfx->PitchMask;
|
2016-03-01 15:47:10 +00:00
|
|
|
rolloff = &sfx->Rolloff;
|
|
|
|
|
|
|
|
// Resolve player sounds, random sounds, and aliases
|
|
|
|
while (sfx->link != sfxinfo_t::NO_LINK)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (sfx->bRandomHeader)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
// Random sounds attenuate based on the original (random) sound as well as the chosen one.
|
|
|
|
attenuation *= sfx->Attenuation;
|
|
|
|
sound_id = FSoundID(S_PickReplacement (sound_id));
|
|
|
|
if (near_limit < 0)
|
|
|
|
{
|
|
|
|
near_limit = S_sfx[sound_id].NearLimit;
|
|
|
|
limit_range = S_sfx[sound_id].LimitRange;
|
|
|
|
}
|
|
|
|
if (rolloff->MinDistance == 0)
|
|
|
|
{
|
|
|
|
rolloff = &S_sfx[sound_id].Rolloff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sound_id = FSoundID(sfx->link);
|
|
|
|
if (near_limit < 0)
|
|
|
|
{
|
|
|
|
near_limit = S_sfx[sound_id].NearLimit;
|
|
|
|
limit_range = S_sfx[sound_id].LimitRange;
|
|
|
|
}
|
|
|
|
if (rolloff->MinDistance == 0)
|
|
|
|
{
|
|
|
|
rolloff = &S_sfx[sound_id].Rolloff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sfx = &S_sfx[sound_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attenuate the attenuation based on the sound.
|
|
|
|
attenuation *= sfx->Attenuation;
|
|
|
|
|
|
|
|
// The passed rolloff overrides any sound-specific rolloff.
|
|
|
|
if (forcedrolloff != NULL && forcedrolloff->MinDistance != 0)
|
|
|
|
{
|
|
|
|
rolloff = forcedrolloff;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no valid rolloff was set, use the global default.
|
|
|
|
if (rolloff->MinDistance == 0)
|
|
|
|
{
|
|
|
|
rolloff = &S_Rolloff;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a singular sound, don't play it if it's already playing.
|
|
|
|
if (sfx->bSingular && S_CheckSingular(sound_id))
|
|
|
|
{
|
|
|
|
chanflags |= CHAN_EVICTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the sound is unpositioned or comes from the listener, it is
|
|
|
|
// never limited.
|
2019-12-08 12:28:52 +00:00
|
|
|
if (type == SOURCE_None || source == listener.ListenerObject)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
near_limit = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this sound doesn't like playing near itself, don't play it if
|
|
|
|
// that's what would happen.
|
2019-12-08 12:28:52 +00:00
|
|
|
if (near_limit > 0 && S_CheckSoundLimit(sfx, pos, near_limit, limit_range, type, type == SOURCE_Actor? source : nullptr, channel))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
chanflags |= CHAN_EVICTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the sound is blocked and not looped, return now. If the sound
|
|
|
|
// is blocked and looped, pretend to play it so that it can
|
|
|
|
// eventually play for real.
|
|
|
|
if ((chanflags & (CHAN_EVICTED | CHAN_LOOP)) == CHAN_EVICTED)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the sound is loaded.
|
2017-04-26 18:51:06 +00:00
|
|
|
sfx = S_LoadSound(sfx, &SoundBuffer);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// The empty sound never plays.
|
|
|
|
if (sfx->lumpnum == sfx_empty)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Select priority.
|
2019-12-08 12:28:52 +00:00
|
|
|
if (type == SOURCE_None || source == listener.ListenerObject)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
basepriority = 80;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
basepriority = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int seen = 0;
|
2019-12-08 12:28:52 +00:00
|
|
|
if (source != NULL && channel == CHAN_AUTO)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
// Select a channel that isn't already playing something.
|
|
|
|
// Try channel 0 first, then travel from channel 7 down.
|
2019-12-08 12:28:52 +00:00
|
|
|
if (!S_IsChannelUsed(type, source, 0, &seen))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
channel = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (channel = 7; channel > 0; --channel)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (!S_IsChannelUsed(type, source, channel, &seen))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (channel == 0)
|
|
|
|
{ // Crap. No free channels.
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this actor is already playing something on the selected channel, stop it.
|
2019-12-08 12:28:52 +00:00
|
|
|
if (type != SOURCE_None && ((source == NULL && channel != CHAN_AUTO) || (source != NULL && S_IsChannelUsed(type, source, channel, &seen))))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
for (chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
|
|
|
if (chan->SourceType == type && chan->EntChannel == channel)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (type != SOURCE_Unattached) chan->Source = source;
|
|
|
|
else
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->Point[0] == pt->X && chan->Point[2] == pt->Z && chan->Point[1] == pt->Y;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sound is paused and a non-looped sound is being started.
|
|
|
|
// Such a sound would play right after unpausing which wouldn't sound right.
|
|
|
|
if (!(chanflags & CHAN_LOOP) && !(chanflags & (CHAN_UI|CHAN_NOPAUSE)) && SoundPaused)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vary the sfx pitches.
|
2016-12-26 20:07:21 +00:00
|
|
|
if (pitchmask != 0)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2016-12-26 20:07:21 +00:00
|
|
|
pitch = NORM_PITCH - (M_Random() & pitchmask) + (M_Random() & pitchmask);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pitch = NORM_PITCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chanflags & CHAN_EVICTED)
|
|
|
|
{
|
|
|
|
chan = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int startflags = 0;
|
|
|
|
if (chanflags & CHAN_LOOP) startflags |= SNDF_LOOP;
|
|
|
|
if (chanflags & CHAN_AREA) startflags |= SNDF_AREA;
|
|
|
|
if (chanflags & (CHAN_UI|CHAN_NOPAUSE)) startflags |= SNDF_NOPAUSE;
|
|
|
|
if (chanflags & CHAN_UI) startflags |= SNDF_NOREVERB;
|
|
|
|
|
|
|
|
if (attenuation > 0)
|
|
|
|
{
|
2017-04-26 18:51:06 +00:00
|
|
|
S_LoadSound3D(sfx, &SoundBuffer);
|
2016-05-01 18:01:59 +00:00
|
|
|
chan = (FSoundChan*)GSnd->StartSound3D (sfx->data3d, &listener, float(volume), rolloff, float(attenuation), pitch, basepriority, pos, vel, channel, startflags, NULL);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chan = (FSoundChan*)GSnd->StartSound (sfx->data, float(volume), pitch, startflags, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (chan == NULL && (chanflags & CHAN_LOOP))
|
|
|
|
{
|
|
|
|
chan = (FSoundChan*)S_GetChannel(NULL);
|
|
|
|
GSnd->MarkStartTime(chan);
|
|
|
|
chanflags |= CHAN_EVICTED;
|
|
|
|
}
|
|
|
|
if (attenuation > 0)
|
|
|
|
{
|
|
|
|
chanflags |= CHAN_IS3D | CHAN_JUSTSTARTED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chanflags |= CHAN_LISTENERZ | CHAN_JUSTSTARTED;
|
|
|
|
}
|
|
|
|
if (chan != NULL)
|
|
|
|
{
|
|
|
|
chan->SoundID = sound_id;
|
|
|
|
chan->OrgID = FSoundID(org_id);
|
|
|
|
chan->EntChannel = channel;
|
|
|
|
chan->Volume = float(volume);
|
|
|
|
chan->ChanFlags |= chanflags;
|
|
|
|
chan->NearLimit = near_limit;
|
|
|
|
chan->LimitRange = limit_range;
|
|
|
|
chan->Pitch = pitch;
|
|
|
|
chan->Priority = basepriority;
|
|
|
|
chan->DistanceScale = float(attenuation);
|
|
|
|
chan->SourceType = type;
|
2019-12-08 12:28:52 +00:00
|
|
|
if (type == SOURCE_Unattached)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->Point[0] = pt->X; chan->Point[1] = pt->Y; chan->Point[2] = pt->Z;
|
|
|
|
}
|
|
|
|
else if (type != SOURCE_None)
|
|
|
|
{
|
|
|
|
chan->Source = source;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2019-07-24 00:40:19 +00:00
|
|
|
|
2019-09-23 12:42:03 +00:00
|
|
|
if (spitch > 0.0)
|
|
|
|
S_SetPitch(chan, spitch);
|
|
|
|
}
|
2019-07-24 00:40:19 +00:00
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_RestartSound
|
|
|
|
//
|
|
|
|
// Attempts to restart looping sounds that were evicted from their channels.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_RestartSound(FSoundChan *chan)
|
|
|
|
{
|
|
|
|
assert(chan->ChanFlags & CHAN_EVICTED);
|
|
|
|
|
|
|
|
FSoundChan *ochan;
|
|
|
|
sfxinfo_t *sfx = &S_sfx[chan->SoundID];
|
2017-04-26 18:51:06 +00:00
|
|
|
FSoundLoadBuffer SoundBuffer;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// If this is a singular sound, don't play it if it's already playing.
|
|
|
|
if (sfx->bSingular && S_CheckSingular(chan->SoundID))
|
|
|
|
return;
|
|
|
|
|
2017-04-26 18:51:06 +00:00
|
|
|
sfx = S_LoadSound(sfx, &SoundBuffer);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// The empty sound never plays.
|
|
|
|
if (sfx->lumpnum == sfx_empty)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int oldflags = chan->ChanFlags;
|
|
|
|
|
|
|
|
int startflags = 0;
|
|
|
|
if (chan->ChanFlags & CHAN_LOOP) startflags |= SNDF_LOOP;
|
|
|
|
if (chan->ChanFlags & CHAN_AREA) startflags |= SNDF_AREA;
|
|
|
|
if (chan->ChanFlags & (CHAN_UI|CHAN_NOPAUSE)) startflags |= SNDF_NOPAUSE;
|
|
|
|
if (chan->ChanFlags & CHAN_ABSTIME) startflags |= SNDF_ABSTIME;
|
|
|
|
|
|
|
|
if (chan->ChanFlags & CHAN_IS3D)
|
|
|
|
{
|
|
|
|
FVector3 pos, vel;
|
|
|
|
|
|
|
|
CalcPosVel(chan, &pos, &vel);
|
|
|
|
|
2018-03-03 10:36:52 +00:00
|
|
|
if (!ValidatePosVel(chan, pos, vel))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
// If this sound doesn't like playing near itself, don't play it if
|
|
|
|
// that's what would happen.
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->NearLimit > 0 && S_CheckSoundLimit(&S_sfx[chan->SoundID], pos, chan->NearLimit, chan->LimitRange, 0, NULL, 0))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-04-26 18:51:06 +00:00
|
|
|
S_LoadSound3D(sfx, &SoundBuffer);
|
2016-03-01 15:47:10 +00:00
|
|
|
chan->ChanFlags &= ~(CHAN_EVICTED|CHAN_ABSTIME);
|
2016-05-01 18:01:59 +00:00
|
|
|
ochan = (FSoundChan*)GSnd->StartSound3D(sfx->data3d, &listener, chan->Volume, &chan->Rolloff, chan->DistanceScale, chan->Pitch,
|
|
|
|
chan->Priority, pos, vel, chan->EntChannel, startflags, chan);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chan->ChanFlags &= ~(CHAN_EVICTED|CHAN_ABSTIME);
|
|
|
|
ochan = (FSoundChan*)GSnd->StartSound(sfx->data, chan->Volume, chan->Pitch, startflags, chan);
|
|
|
|
}
|
|
|
|
assert(ochan == NULL || ochan == chan);
|
|
|
|
if (ochan == NULL)
|
|
|
|
{
|
|
|
|
chan->ChanFlags = oldflags;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_LoadSound
|
|
|
|
//
|
|
|
|
// Returns a pointer to the sfxinfo with the actual sound data.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2017-04-26 18:51:06 +00:00
|
|
|
sfxinfo_t *S_LoadSound(sfxinfo_t *sfx, FSoundLoadBuffer *pBuffer)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (GSnd->IsNull()) return sfx;
|
|
|
|
|
|
|
|
while (!sfx->data.isValid())
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
// If the sound doesn't exist, replace it with the empty sound.
|
|
|
|
if (sfx->lumpnum == -1)
|
|
|
|
{
|
|
|
|
sfx->lumpnum = sfx_empty;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if there is another sound already initialized with this lump. If so,
|
|
|
|
// then set this one up as a link, and don't load the sound again.
|
|
|
|
for (i = 0; i < S_sfx.Size(); i++)
|
|
|
|
{
|
|
|
|
if (S_sfx[i].data.isValid() && S_sfx[i].link == sfxinfo_t::NO_LINK && S_sfx[i].lumpnum == sfx->lumpnum)
|
|
|
|
{
|
2016-08-28 07:55:04 +00:00
|
|
|
DPrintf (DMSG_NOTIFY, "Linked %s to %s (%d)\n", sfx->name.GetChars(), S_sfx[i].name.GetChars(), i);
|
2016-03-01 15:47:10 +00:00
|
|
|
sfx->link = i;
|
|
|
|
// This is necessary to avoid using the rolloff settings of the linked sound if its
|
|
|
|
// settings are different.
|
|
|
|
if (sfx->Rolloff.MinDistance == 0) sfx->Rolloff = S_Rolloff;
|
|
|
|
return &S_sfx[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-28 07:55:04 +00:00
|
|
|
DPrintf(DMSG_NOTIFY, "Loading sound \"%s\" (%td)\n", sfx->name.GetChars(), sfx - &S_sfx[0]);
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
int size = Wads.LumpLength(sfx->lumpnum);
|
2019-03-02 20:09:45 +00:00
|
|
|
if (size > 8)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2018-03-10 17:45:11 +00:00
|
|
|
auto wlump = Wads.OpenLumpReader(sfx->lumpnum);
|
2018-11-30 16:07:42 +00:00
|
|
|
auto sfxdata = wlump.Read(size);
|
|
|
|
int32_t dmxlen = LittleLong(((int32_t *)sfxdata.Data())[1]);
|
2016-05-01 18:01:59 +00:00
|
|
|
std::pair<SoundHandle,bool> snd;
|
2016-03-01 15:47:10 +00:00
|
|
|
|
|
|
|
// If the sound is voc, use the custom loader.
|
2018-11-30 16:07:42 +00:00
|
|
|
if (strncmp ((const char *)sfxdata.Data(), "Creative Voice File", 19) == 0)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSoundVoc(sfxdata.Data(), size);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
// If the sound is raw, just load it as such.
|
|
|
|
else if (sfx->bLoadRAW)
|
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSoundRaw(sfxdata.Data(), size, sfx->RawRate, 1, 8, sfx->LoopStart);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
// Otherwise, try the sound as DMX format.
|
2018-11-30 16:07:42 +00:00
|
|
|
else if (((uint8_t *)sfxdata.Data())[0] == 3 && ((uint8_t *)sfxdata.Data())[1] == 0 && dmxlen <= size - 8)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
int frequency = LittleShort(((uint16_t *)sfxdata.Data())[1]);
|
2016-03-01 15:47:10 +00:00
|
|
|
if (frequency == 0) frequency = 11025;
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSoundRaw(sfxdata.Data()+8, dmxlen, frequency, 1, 8, sfx->LoopStart);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
// If that fails, let the sound system try and figure it out.
|
|
|
|
else
|
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSound(sfxdata.Data(), size, false, pBuffer);
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2016-05-01 18:01:59 +00:00
|
|
|
|
|
|
|
sfx->data = snd.first;
|
|
|
|
if(snd.second)
|
|
|
|
sfx->data3d = sfx->data;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!sfx->data.isValid())
|
|
|
|
{
|
|
|
|
if (sfx->lumpnum != sfx_empty)
|
|
|
|
{
|
|
|
|
sfx->lumpnum = sfx_empty;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return sfx;
|
|
|
|
}
|
|
|
|
|
2017-04-26 18:51:06 +00:00
|
|
|
static void S_LoadSound3D(sfxinfo_t *sfx, FSoundLoadBuffer *pBuffer)
|
2016-05-01 18:01:59 +00:00
|
|
|
{
|
|
|
|
if (GSnd->IsNull()) return;
|
|
|
|
|
|
|
|
if(sfx->data3d.isValid())
|
|
|
|
return;
|
|
|
|
|
2016-08-28 07:55:04 +00:00
|
|
|
DPrintf(DMSG_NOTIFY, "Loading monoized sound \"%s\" (%td)\n", sfx->name.GetChars(), sfx - &S_sfx[0]);
|
2016-05-01 18:01:59 +00:00
|
|
|
|
2017-04-29 08:44:13 +00:00
|
|
|
std::pair<SoundHandle, bool> snd;
|
|
|
|
|
2019-09-29 10:48:12 +00:00
|
|
|
if (pBuffer->mBuffer.size() > 0)
|
2017-04-26 18:51:06 +00:00
|
|
|
{
|
2017-04-29 08:44:13 +00:00
|
|
|
snd = GSnd->LoadSoundBuffered(pBuffer, true);
|
2017-04-26 18:51:06 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int size = Wads.LumpLength(sfx->lumpnum);
|
2019-03-02 20:09:45 +00:00
|
|
|
if (size <= 8) return;
|
2017-04-26 18:51:06 +00:00
|
|
|
|
2018-03-10 17:45:11 +00:00
|
|
|
auto wlump = Wads.OpenLumpReader(sfx->lumpnum);
|
2018-11-30 16:07:42 +00:00
|
|
|
auto sfxdata = wlump.Read(size);
|
|
|
|
int32_t dmxlen = LittleLong(((int32_t *)sfxdata.Data())[1]);
|
2017-04-26 18:51:06 +00:00
|
|
|
|
|
|
|
// If the sound is voc, use the custom loader.
|
2018-11-30 16:07:42 +00:00
|
|
|
if (strncmp((const char *)sfxdata.Data(), "Creative Voice File", 19) == 0)
|
2017-04-26 18:51:06 +00:00
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSoundVoc(sfxdata.Data(), size, true);
|
2017-04-26 18:51:06 +00:00
|
|
|
}
|
|
|
|
// If the sound is raw, just load it as such.
|
|
|
|
else if (sfx->bLoadRAW)
|
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSoundRaw(sfxdata.Data(), size, sfx->RawRate, 1, 8, sfx->LoopStart, true);
|
2017-04-26 18:51:06 +00:00
|
|
|
}
|
|
|
|
// Otherwise, try the sound as DMX format.
|
2018-11-30 16:07:42 +00:00
|
|
|
else if (((uint8_t *)sfxdata.Data())[0] == 3 && ((uint8_t *)sfxdata.Data())[1] == 0 && dmxlen <= size - 8)
|
2017-04-26 18:51:06 +00:00
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
int frequency = LittleShort(((uint16_t *)sfxdata.Data())[1]);
|
2017-04-26 18:51:06 +00:00
|
|
|
if (frequency == 0) frequency = 11025;
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSoundRaw(sfxdata.Data() + 8, dmxlen, frequency, 1, 8, sfx->LoopStart, -1, true);
|
2017-04-26 18:51:06 +00:00
|
|
|
}
|
|
|
|
// If that fails, let the sound system try and figure it out.
|
|
|
|
else
|
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
snd = GSnd->LoadSound(sfxdata.Data(), size, true, pBuffer);
|
2017-04-26 18:51:06 +00:00
|
|
|
}
|
|
|
|
}
|
2017-04-29 08:44:13 +00:00
|
|
|
|
|
|
|
sfx->data3d = snd.first;
|
2016-05-01 18:01:59 +00:00
|
|
|
}
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_CheckSingular
|
|
|
|
//
|
|
|
|
// Returns true if a copy of this sound is already playing.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool S_CheckSingular(int sound_id)
|
|
|
|
{
|
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
|
|
|
if (chan->OrgID == sound_id)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_CheckSoundLimit
|
|
|
|
//
|
|
|
|
// Limits the number of nearby copies of a sound that can play near
|
|
|
|
// each other. If there are NearLimit instances of this sound already
|
|
|
|
// playing within sqrt(limit_range) (typically 256 units) of the new sound, the
|
|
|
|
// new sound will not start.
|
|
|
|
//
|
|
|
|
// If an actor is specified, and it is already playing the same sound on
|
|
|
|
// the same channel, this sound will not be limited. In this case, we're
|
|
|
|
// restarting an already playing sound, so there's no need to limit it.
|
|
|
|
//
|
|
|
|
// Returns true if the sound should not play.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool S_CheckSoundLimit(sfxinfo_t *sfx, const FVector3 &pos, int near_limit, float limit_range,
|
2019-12-08 12:28:52 +00:00
|
|
|
int sourcetype, const void *actor, int channel)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
FSoundChan *chan;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
for (chan = Channels, count = 0; chan != NULL && count < near_limit; chan = chan->NextChan)
|
|
|
|
{
|
|
|
|
if (!(chan->ChanFlags & CHAN_EVICTED) && &S_sfx[chan->SoundID] == sfx)
|
|
|
|
{
|
|
|
|
FVector3 chanorigin;
|
|
|
|
|
|
|
|
if (actor != NULL && chan->EntChannel == channel &&
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->SourceType == sourcetype && chan->Source == actor)
|
2016-03-01 15:47:10 +00:00
|
|
|
{ // We are restarting a playing sound. Always let it play.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
CalcPosVel(chan, &chanorigin, NULL);
|
|
|
|
if ((chanorigin - pos).LengthSquared() <= limit_range)
|
|
|
|
{
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count >= near_limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopSound
|
|
|
|
//
|
|
|
|
// Stops an unpositioned sound from playing on a specific channel.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_StopSound (int channel)
|
|
|
|
{
|
|
|
|
FSoundChan *chan = Channels;
|
|
|
|
while (chan != NULL)
|
|
|
|
{
|
|
|
|
FSoundChan *next = chan->NextChan;
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->SourceType == SOURCE_None)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
S_StopChannel(chan);
|
|
|
|
}
|
|
|
|
chan = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopSound
|
|
|
|
//
|
|
|
|
// Stops a sound from a single actor from playing on a specific channel.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void S_StopSound(int sourcetype, const void* actor, int channel)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
FSoundChan* chan = Channels;
|
2016-03-01 15:47:10 +00:00
|
|
|
while (chan != NULL)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
FSoundChan* next = chan->NextChan;
|
|
|
|
if (chan->SourceType == sourcetype &&
|
|
|
|
chan->Source == actor &&
|
|
|
|
(chan->EntChannel == channel || channel < 0))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
S_StopChannel(chan);
|
|
|
|
}
|
|
|
|
chan = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopAllChannels
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_StopAllChannels ()
|
|
|
|
{
|
|
|
|
SN_StopAllSequences();
|
|
|
|
|
|
|
|
FSoundChan *chan = Channels;
|
|
|
|
while (chan != NULL)
|
|
|
|
{
|
|
|
|
FSoundChan *next = chan->NextChan;
|
|
|
|
S_StopChannel(chan);
|
|
|
|
chan = next;
|
|
|
|
}
|
2019-10-09 12:05:20 +00:00
|
|
|
|
|
|
|
if (GSnd)
|
|
|
|
GSnd->UpdateSounds();
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_RelinkSound
|
|
|
|
//
|
|
|
|
// Moves all the sounds from one thing to another. If the destination is
|
|
|
|
// NULL, then the sound becomes a positioned sound.
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void S_RelinkSound (int sourcetype, const void *from, const void *to, const FVector3 *optpos)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (from == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
FSoundChan *chan = Channels;
|
|
|
|
while (chan != NULL)
|
|
|
|
{
|
|
|
|
FSoundChan *next = chan->NextChan;
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->SourceType == sourcetype && chan->Source == from)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (to != NULL)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->Source = to;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
2019-12-08 12:28:52 +00:00
|
|
|
else if (!(chan->ChanFlags & CHAN_LOOP) && optpos)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->Source = NULL;
|
2016-03-01 15:47:10 +00:00
|
|
|
chan->SourceType = SOURCE_Unattached;
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->Point[0] = optpos->X;
|
|
|
|
chan->Point[1] = optpos->Y;
|
|
|
|
chan->Point[2] = optpos->Z;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
S_StopChannel(chan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chan = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ChangeSoundVolume
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void S_ChangeSoundVolume(int sourcetype, const void *source, int channel, double dvolume)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2018-12-05 16:34:11 +00:00
|
|
|
float volume = float(dvolume);
|
2017-11-10 13:21:28 +00:00
|
|
|
// don't let volume get out of bounds
|
|
|
|
if (volume < 0.0)
|
|
|
|
volume = 0.0;
|
|
|
|
else if (volume > 1.0)
|
|
|
|
volume = 1.0;
|
|
|
|
|
2016-03-01 15:47:10 +00:00
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->SourceType == sourcetype &&
|
|
|
|
chan->Source == source &&
|
|
|
|
(chan->EntChannel == channel || channel == -1))
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
GSnd->ChannelVolume(chan, volume);
|
|
|
|
chan->Volume = volume;
|
2018-12-05 19:10:44 +00:00
|
|
|
return;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
2018-12-05 19:10:44 +00:00
|
|
|
return;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
2019-07-24 00:40:19 +00:00
|
|
|
//
|
|
|
|
// S_ChangeSoundPitch
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void S_ChangeSoundPitch(int sourcetype, const void *source, int channel, double pitch)
|
2019-07-24 00:40:19 +00:00
|
|
|
{
|
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->SourceType == sourcetype &&
|
|
|
|
chan->Source == source &&
|
2019-07-24 00:40:19 +00:00
|
|
|
chan->EntChannel == channel)
|
|
|
|
{
|
|
|
|
S_SetPitch(chan, (float)pitch);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void S_SetPitch(FSoundChan *chan, float pitch)
|
|
|
|
{
|
2019-09-23 12:42:03 +00:00
|
|
|
assert(chan != nullptr);
|
2019-07-24 00:40:19 +00:00
|
|
|
GSnd->ChannelPitch(chan, MAX(0.0001f, pitch));
|
|
|
|
chan->Pitch = MAX(1, int(float(NORM_PITCH) * pitch));
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
2016-03-01 15:47:10 +00:00
|
|
|
//
|
|
|
|
// S_GetSoundPlayingInfo
|
|
|
|
//
|
|
|
|
// Is a sound being played by a specific emitter?
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
bool S_GetSoundPlayingInfo (int sourcetype, const void *source, int sound_id)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (sound_id > 0)
|
|
|
|
{
|
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
|
|
|
if (chan->OrgID == sound_id &&
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->SourceType == sourcetype &&
|
|
|
|
chan->Source == source)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_IsChannelUsed
|
|
|
|
//
|
|
|
|
// Returns true if the channel is in use. Also fills in a bitmask of
|
|
|
|
// channels seen while scanning for this one, to make searching for unused
|
|
|
|
// channels faster. Initialize seen to 0 for the first call.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
static bool S_IsChannelUsed(int sourcetype, const void *actor, int channel, int *seen)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (*seen & (1 << channel))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->SourceType == sourcetype && chan->Source == actor)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
*seen |= 1 << chan->EntChannel;
|
|
|
|
if (chan->EntChannel == channel)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_IsActorPlayingSomething
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
bool S_IsSourcePlayingSomething (int sourcetype, const void *actor, int channel, int sound_id)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chan->SourceType == sourcetype && chan->Source == actor)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if (channel == 0 || chan->EntChannel == channel)
|
|
|
|
{
|
|
|
|
return sound_id <= 0 || chan->OrgID == sound_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_SetSoundPaused
|
|
|
|
//
|
|
|
|
// Called with state non-zero when the app is active, zero when it isn't.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_SetSoundPaused (int state)
|
|
|
|
{
|
|
|
|
if (state)
|
|
|
|
{
|
|
|
|
if (paused == 0)
|
|
|
|
{
|
|
|
|
S_ResumeSound(true);
|
|
|
|
if (GSnd != NULL)
|
|
|
|
{
|
|
|
|
GSnd->SetInactive(SoundRenderer::INACTIVE_Active);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (paused == 0)
|
|
|
|
{
|
|
|
|
S_PauseSound(false, true);
|
|
|
|
if (GSnd != NULL)
|
|
|
|
{
|
|
|
|
GSnd->SetInactive(gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL ?
|
|
|
|
SoundRenderer::INACTIVE_Complete :
|
|
|
|
SoundRenderer::INACTIVE_Mute);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!netgame
|
|
|
|
#ifdef _DEBUG
|
|
|
|
&& !demoplayback
|
|
|
|
#endif
|
|
|
|
)
|
|
|
|
{
|
|
|
|
pauseext = !state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_EvictAllChannels
|
|
|
|
//
|
|
|
|
// Forcibly evicts all channels so that there are none playing, but all
|
|
|
|
// information needed to restart them is retained.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_EvictAllChannels()
|
|
|
|
{
|
|
|
|
FSoundChan *chan, *next;
|
|
|
|
|
|
|
|
for (chan = Channels; chan != NULL; chan = next)
|
|
|
|
{
|
|
|
|
next = chan->NextChan;
|
|
|
|
|
|
|
|
if (!(chan->ChanFlags & CHAN_EVICTED))
|
|
|
|
{
|
|
|
|
chan->ChanFlags |= CHAN_EVICTED;
|
|
|
|
if (chan->SysChannel != NULL)
|
|
|
|
{
|
|
|
|
if (!(chan->ChanFlags & CHAN_ABSTIME))
|
|
|
|
{
|
2019-06-26 20:13:12 +00:00
|
|
|
chan->StartTime = GSnd ? GSnd->GetPosition(chan) : 0;
|
2016-03-01 15:47:10 +00:00
|
|
|
chan->ChanFlags |= CHAN_ABSTIME;
|
|
|
|
}
|
|
|
|
S_StopChannel(chan);
|
|
|
|
}
|
|
|
|
// assert(chan->NextChan == next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_RestoreEvictedChannel
|
|
|
|
//
|
|
|
|
// Recursive helper for S_RestoreEvictedChannels().
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_RestoreEvictedChannel(FSoundChan *chan)
|
|
|
|
{
|
|
|
|
if (chan == NULL)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
S_RestoreEvictedChannel(chan->NextChan);
|
|
|
|
if (chan->ChanFlags & CHAN_EVICTED)
|
|
|
|
{
|
|
|
|
S_RestartSound(chan);
|
|
|
|
if (!(chan->ChanFlags & CHAN_LOOP))
|
|
|
|
{
|
|
|
|
if (chan->ChanFlags & CHAN_EVICTED)
|
|
|
|
{ // Still evicted and not looping? Forget about it.
|
|
|
|
S_ReturnChannel(chan);
|
|
|
|
}
|
|
|
|
else if (!(chan->ChanFlags & CHAN_JUSTSTARTED))
|
|
|
|
{ // Should this sound become evicted again, it's okay to forget about it.
|
|
|
|
chan->ChanFlags |= CHAN_FORGETTABLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (chan->SysChannel == NULL && (chan->ChanFlags & (CHAN_FORGETTABLE | CHAN_LOOP)) == CHAN_FORGETTABLE)
|
|
|
|
{
|
|
|
|
S_ReturnChannel(chan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_RestoreEvictedChannels
|
|
|
|
//
|
|
|
|
// Restarts as many evicted channels as possible. Any channels that could
|
|
|
|
// not be started and are not looping are moved to the free pool.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_RestoreEvictedChannels()
|
|
|
|
{
|
|
|
|
// Restart channels in the same order they were originally played.
|
|
|
|
S_RestoreEvictedChannel(Channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_UpdateSounds
|
|
|
|
//
|
|
|
|
// Updates music & sounds
|
|
|
|
//==========================================================================
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void S_UpdateSounds(int time)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
FVector3 pos, vel;
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
for (FSoundChan* chan = Channels; chan != NULL; chan = chan->NextChan)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
if ((chan->ChanFlags & (CHAN_EVICTED | CHAN_IS3D)) == CHAN_IS3D)
|
|
|
|
{
|
|
|
|
CalcPosVel(chan, &pos, &vel);
|
2018-03-03 10:36:52 +00:00
|
|
|
|
|
|
|
if (ValidatePosVel(chan, pos, vel))
|
|
|
|
{
|
|
|
|
GSnd->UpdateSoundParams3D(&listener, chan, !!(chan->ChanFlags & CHAN_AREA), pos, vel);
|
|
|
|
}
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
chan->ChanFlags &= ~CHAN_JUSTSTARTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GSnd->UpdateListener(&listener);
|
|
|
|
GSnd->UpdateSounds();
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
if (time >= RestartEvictionsAt)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
|
|
|
RestartEvictionsAt = 0;
|
|
|
|
S_RestoreEvictedChannels();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_GetRolloff
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
float S_GetRolloff(FRolloffInfo *rolloff, float distance, bool logarithmic)
|
|
|
|
{
|
|
|
|
if (rolloff == NULL)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (distance <= rolloff->MinDistance)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (rolloff->RolloffType == ROLLOFF_Log)
|
|
|
|
{ // Logarithmic rolloff has no max distance where it goes silent.
|
|
|
|
return rolloff->MinDistance / (rolloff->MinDistance + rolloff->RolloffFactor * (distance - rolloff->MinDistance));
|
|
|
|
}
|
|
|
|
if (distance >= rolloff->MaxDistance)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance);
|
2018-11-30 16:07:42 +00:00
|
|
|
if (rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve.Size() > 0)
|
2016-03-01 15:47:10 +00:00
|
|
|
{
|
2018-11-30 16:07:42 +00:00
|
|
|
volume = S_SoundCurve[int(S_SoundCurve.Size() * (1 - volume))] / 127.f;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
if (logarithmic)
|
|
|
|
{
|
|
|
|
if (rolloff->RolloffType == ROLLOFF_Linear)
|
|
|
|
{
|
|
|
|
return volume;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return float((powf(10.f, volume) - 1.) / 9.);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (rolloff->RolloffType == ROLLOFF_Linear)
|
|
|
|
{
|
|
|
|
return float(log10(9. * volume + 1.));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return volume;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ChannelEnded (callback for sound interface code)
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_ChannelEnded(FISoundChannel *ichan)
|
|
|
|
{
|
|
|
|
FSoundChan *schan = static_cast<FSoundChan*>(ichan);
|
|
|
|
bool evicted;
|
|
|
|
|
|
|
|
if (schan != NULL)
|
|
|
|
{
|
|
|
|
// If the sound was stopped with GSnd->StopSound(), then we know
|
|
|
|
// it wasn't evicted. Otherwise, if it's looping, it must have
|
|
|
|
// been evicted. If it's not looping, then it was evicted if it
|
|
|
|
// didn't reach the end of its playback.
|
|
|
|
if (schan->ChanFlags & CHAN_FORGETTABLE)
|
|
|
|
{
|
|
|
|
evicted = false;
|
|
|
|
}
|
|
|
|
else if (schan->ChanFlags & (CHAN_LOOP | CHAN_EVICTED))
|
|
|
|
{
|
|
|
|
evicted = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned int pos = GSnd->GetPosition(schan);
|
|
|
|
unsigned int len = GSnd->GetSampleLength(S_sfx[schan->SoundID].data);
|
|
|
|
if (pos == 0)
|
|
|
|
{
|
|
|
|
evicted = !!(schan->ChanFlags & CHAN_JUSTSTARTED);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
evicted = (pos < len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!evicted)
|
|
|
|
{
|
|
|
|
S_ReturnChannel(schan);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
schan->ChanFlags |= CHAN_EVICTED;
|
|
|
|
schan->SysChannel = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ChannelVirtualChanged (callback for sound interface code)
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_ChannelVirtualChanged(FISoundChannel *ichan, bool is_virtual)
|
|
|
|
{
|
|
|
|
FSoundChan *schan = static_cast<FSoundChan*>(ichan);
|
|
|
|
if (is_virtual)
|
|
|
|
{
|
|
|
|
schan->ChanFlags |= CHAN_VIRTUAL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
schan->ChanFlags &= ~CHAN_VIRTUAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopChannel
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_StopChannel(FSoundChan *chan)
|
|
|
|
{
|
|
|
|
if (chan == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (chan->SysChannel != NULL)
|
|
|
|
{
|
|
|
|
// S_EvictAllChannels() will set the CHAN_EVICTED flag to indicate
|
|
|
|
// that it wants to keep all the channel information around.
|
|
|
|
if (!(chan->ChanFlags & CHAN_EVICTED))
|
|
|
|
{
|
|
|
|
chan->ChanFlags |= CHAN_FORGETTABLE;
|
|
|
|
if (chan->SourceType == SOURCE_Actor)
|
|
|
|
{
|
2019-12-08 12:28:52 +00:00
|
|
|
chan->Source = NULL;
|
2016-03-01 15:47:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
GSnd->StopChannel(chan);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
S_ReturnChannel(chan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-23 19:57:56 +00:00
|
|
|
void S_UnloadAllSounds()
|
|
|
|
{
|
|
|
|
for (unsigned i = 0; i < S_sfx.Size(); i++)
|
|
|
|
{
|
|
|
|
S_UnloadSound(&S_sfx[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
void S_Reset()
|
2019-08-23 19:57:56 +00:00
|
|
|
{
|
|
|
|
S_EvictAllChannels();
|
|
|
|
I_CloseSound();
|
|
|
|
I_InitSound();
|
|
|
|
S_RestoreEvictedChannels();
|
|
|
|
}
|
|
|
|
|