2019-12-08 12:28:52 +00:00
|
|
|
/*
|
|
|
|
** doomspund.cpp
|
|
|
|
**
|
|
|
|
** Game dependent part of the sound engine.
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
** Copyright 1999-2016 Randy Heit
|
|
|
|
** Copyright 2002-2019 Christoph Oelckers
|
|
|
|
**
|
|
|
|
** 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.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <io.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#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"
|
|
|
|
#include "serializer.h"
|
|
|
|
#include "d_player.h"
|
|
|
|
#include "g_levellocals.h"
|
|
|
|
#include "vm.h"
|
|
|
|
#include "g_game.h"
|
|
|
|
#include "s_music.h"
|
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
FBoolCVar noisedebug("noise", false, 0); // [RH] Print sound debugging info?
|
|
|
|
CUSTOM_CVAR(Int, snd_channels, 128, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // number of channels available
|
|
|
|
{
|
|
|
|
if (self < 64) self = 64;
|
|
|
|
}
|
|
|
|
CVAR(Bool, snd_waterreverb, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
|
|
|
|
|
|
|
|
|
|
static FString LastLocalSndInfo;
|
|
|
|
static FString LastLocalSndSeq;
|
|
|
|
void S_AddLocalSndInfo(int lump);
|
|
|
|
|
|
|
|
class DoomSoundEngine : public SoundEngine
|
|
|
|
{
|
|
|
|
// client specific parts of the sound engine go in this class.
|
|
|
|
void CalcPosVel(int type, const void* source, const float pt[3], int channum, int chanflags, FVector3* pos, FVector3* vel) override;
|
|
|
|
bool ValidatePosVel(int sourcetype, const void* source, const FVector3& pos, const FVector3& vel);
|
|
|
|
TArray<uint8_t> ReadSound(int lumpnum);
|
|
|
|
int PickReplacement(int refid);
|
2019-12-12 00:31:41 +00:00
|
|
|
int ResolveSound(const void *ent, int type, sfxinfo_t *sfx, float &attenuation);
|
2019-12-08 20:22:53 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
DoomSoundEngine() = default;
|
|
|
|
void NoiseDebug(void);
|
|
|
|
void PrintSoundList();
|
|
|
|
};
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Init
|
|
|
|
//
|
|
|
|
// Initializes sound stuff, including volume. Sets channels, SFX and
|
|
|
|
// music volume, allocates channel buffer, and sets S_sfx lookup.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Init()
|
|
|
|
{
|
|
|
|
// Must be up before I_InitSound.
|
|
|
|
if (!soundEngine)
|
|
|
|
{
|
|
|
|
soundEngine = new DoomSoundEngine;
|
|
|
|
}
|
|
|
|
|
|
|
|
I_InitSound();
|
|
|
|
|
|
|
|
// Heretic and Hexen have sound curve lookup tables. Doom does not.
|
|
|
|
int curvelump = Wads.CheckNumForName("SNDCURVE");
|
|
|
|
TArray<uint8_t> curve;
|
|
|
|
if (curvelump >= 0)
|
|
|
|
{
|
|
|
|
curve = Wads.ReadLumpIntoArray(curvelump);
|
|
|
|
}
|
|
|
|
soundEngine->Init(curve);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Shutdown
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Shutdown()
|
|
|
|
{
|
|
|
|
S_StopMusic(true);
|
|
|
|
|
|
|
|
SN_StopAllSequences();
|
|
|
|
|
|
|
|
if (soundEngine)
|
|
|
|
{
|
|
|
|
soundEngine->Shutdown();
|
|
|
|
delete soundEngine;
|
|
|
|
soundEngine = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
mus_playing.LastSong = ""; // If this isn't reset here, the song would attempt to resume at the most inpopportune time...
|
|
|
|
|
|
|
|
if (GSnd != nullptr)
|
|
|
|
{
|
|
|
|
I_CloseSound();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Start
|
|
|
|
//
|
|
|
|
// Per level startup code. Kills playing sounds at start of level
|
|
|
|
// and starts new music.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Start()
|
|
|
|
{
|
|
|
|
if (GSnd && soundEngine)
|
|
|
|
{
|
|
|
|
// kill all playing sounds at start of level (trust me - a good idea)
|
|
|
|
soundEngine->StopAllChannels();
|
|
|
|
|
|
|
|
// Check for local sound definitions. Only reload if they differ
|
|
|
|
// from the previous ones.
|
|
|
|
FString LocalSndInfo;
|
|
|
|
FString LocalSndSeq;
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
soundEngine->UnloadAllSounds();
|
|
|
|
|
|
|
|
// Parse the global SNDINFO
|
|
|
|
S_ParseSndInfo(true);
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
if (GSnd)
|
|
|
|
{
|
|
|
|
soundEngine->MarkAllUnused();
|
|
|
|
|
|
|
|
AActor* actor;
|
|
|
|
TThinkerIterator<AActor> iterator;
|
|
|
|
|
|
|
|
// Precache all sounds known to be used by the currently spawned actors.
|
|
|
|
while ((actor = iterator.Next()) != nullptr)
|
|
|
|
{
|
|
|
|
IFVIRTUALPTR(actor, AActor, MarkPrecacheSounds)
|
|
|
|
{
|
|
|
|
VMValue params[1] = { actor };
|
|
|
|
VMCall(func, params, 1, nullptr, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto snd : gameinfo.PrecachedSounds)
|
|
|
|
{
|
|
|
|
soundEngine->MarkUsed(snd);
|
|
|
|
}
|
|
|
|
// Precache all extra sounds requested by this map.
|
|
|
|
for (auto snd : level.info->PrecacheSounds)
|
|
|
|
{
|
|
|
|
soundEngine->MarkUsed(snd);
|
|
|
|
}
|
|
|
|
soundEngine->CacheMarkedSounds();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_InitData
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_InitData()
|
|
|
|
{
|
|
|
|
LastLocalSndInfo = LastLocalSndSeq = "";
|
|
|
|
S_ParseSndInfo(false);
|
|
|
|
S_ParseSndSeq(-1);
|
|
|
|
}
|
2019-12-08 12:28:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Sound - Unpositioned version
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_SoundPitch(int channel, FSoundID sound_id, float volume, float attenuation, float pitch)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, channel, sound_id, volume, attenuation, 0, pitch);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void S_Sound(int channel, FSoundID sound_id, float volume, float attenuation)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StartSound (SOURCE_None, nullptr, nullptr, channel, sound_id, volume, attenuation, 0, 0.f);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(DObject, S_Sound)
|
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_SOUND(id);
|
|
|
|
PARAM_INT(channel);
|
|
|
|
PARAM_FLOAT(volume);
|
|
|
|
PARAM_FLOAT(attn);
|
|
|
|
PARAM_FLOAT(pitch);
|
|
|
|
S_SoundPitch(channel, id, static_cast<float>(volume), static_cast<float>(attn), static_cast<float>(pitch));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-12-12 00:31:41 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
int DoomSoundEngine::ResolveSound(const void * ent, int type, sfxinfo_t *sfx, float &attenuation)
|
|
|
|
{
|
|
|
|
if (isPlayerReserve(sfx->index))
|
|
|
|
{
|
|
|
|
AActor *src;
|
|
|
|
if (type != SOURCE_Actor) src = nullptr;
|
|
|
|
else src = (AActor*)ent;
|
|
|
|
return S_FindSkinnedSound(src, sfx->index);
|
|
|
|
}
|
|
|
|
return SoundEngine::ResolveSound(ent, type, sfx, attenuation);
|
|
|
|
}
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
2019-12-08 20:22:53 +00:00
|
|
|
// Common checking code for the actor sound functions
|
2019-12-08 12:28:52 +00:00
|
|
|
//
|
|
|
|
//==========================================================================
|
2019-12-08 20:22:53 +00:00
|
|
|
|
|
|
|
static bool VerifyActorSound(AActor* ent, FSoundID& sound_id, int& channel)
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
if (ent == nullptr || ent->Sector->Flags & SECF_SILENT)
|
2019-12-08 20:22:53 +00:00
|
|
|
return false;
|
2019-12-08 12:28:52 +00:00
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
if ((channel & CHAN_MAYBE_LOCAL) && (i_compatflags & COMPATF_SILENTPICKUP))
|
|
|
|
{
|
|
|
|
if (soundEngine->isListener(ent))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i_compatflags & COMPATF_MAGICSILENCE)
|
|
|
|
{ // For people who just can't play without a silent BFG.
|
|
|
|
channel = CHAN_WEAPON;
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
2019-12-08 20:22:53 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Sound - An actor is source
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_SoundPitchActor(AActor *ent, int channel, FSoundID sound_id, float volume, float attenuation, float pitch)
|
|
|
|
{
|
|
|
|
if (VerifyActorSound(ent, sound_id, channel))
|
|
|
|
soundEngine->StartSound (SOURCE_Actor, ent, nullptr, channel, sound_id, volume, attenuation, 0, pitch);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void S_Sound(AActor *ent, int channel, FSoundID sound_id, float volume, float attenuation)
|
|
|
|
{
|
|
|
|
S_SoundPitchActor(ent, channel, sound_id, volume, attenuation, 0.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_SoundMinMaxDist - An actor is source
|
|
|
|
//
|
|
|
|
// Attenuation is specified as min and max distances, rather than a scalar.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_SoundMinMaxDist(AActor *ent, int channel, FSoundID sound_id, float volume, float mindist, float maxdist)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
if (VerifyActorSound(ent, sound_id, channel))
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
FRolloffInfo rolloff;
|
2019-12-08 12:28:52 +00:00
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
rolloff.RolloffType = ROLLOFF_Linear;
|
|
|
|
rolloff.MinDistance = mindist;
|
|
|
|
rolloff.MaxDistance = maxdist;
|
|
|
|
soundEngine->StartSound(SOURCE_Actor, ent, nullptr, channel, sound_id, volume, 1, &rolloff);
|
|
|
|
}
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Sound - A polyobject is source
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Sound (const FPolyObj *poly, int channel, FSoundID sound_id, float volume, float attenuation)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StartSound (SOURCE_Polyobj, poly, nullptr, channel, sound_id, volume, attenuation);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Sound - A point is source
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Sound(const DVector3 &pos, int channel, FSoundID sound_id, float volume, float attenuation)
|
|
|
|
{
|
|
|
|
// The sound system switches Y and Z around.
|
|
|
|
FVector3 p((float)pos.X, (float)pos.Z, (float)pos.Y);
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StartSound (SOURCE_Unattached, nullptr, &p, channel, sound_id, volume, attenuation);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_Sound - An entire sector is source
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_Sound (const sector_t *sec, int channel, FSoundID sfxid, float volume, float attenuation)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StartSound (SOURCE_Sector, sec, nullptr, channel, sfxid, volume, attenuation);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_PlaySound - Subfunction used by ACS and DECORATE
|
|
|
|
//
|
|
|
|
// Has a local parameter to make the sound audible only to the source
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_PlaySoundPitch(AActor *a, int chan, FSoundID sid, float vol, float atten, bool local, float pitch)
|
|
|
|
{
|
|
|
|
if (a == nullptr || a->Sector->Flags & SECF_SILENT)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!local)
|
|
|
|
{
|
|
|
|
S_SoundPitchActor(a, chan, sid, vol, atten, pitch);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (a->CheckLocalView())
|
|
|
|
{
|
|
|
|
S_SoundPitch(chan, sid, vol, ATTN_NONE, pitch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void S_PlaySound(AActor *a, int chan, FSoundID sid, float vol, float atten, bool local)
|
|
|
|
{
|
|
|
|
S_PlaySoundPitch(a, chan, sid, vol, atten, local, 0.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
void A_PlaySound(AActor *self, int soundid, int channel, double volume, int looping, double attenuation, int local, double pitch)
|
|
|
|
{
|
|
|
|
if (!looping)
|
|
|
|
{
|
|
|
|
if (!(channel & CHAN_NOSTOP) || !S_IsActorPlayingSomething(self, channel & 7, soundid))
|
|
|
|
{
|
|
|
|
S_PlaySoundPitch(self, channel, soundid, (float)volume, (float)attenuation, local, (float)pitch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!S_IsActorPlayingSomething(self, channel & 7, soundid))
|
|
|
|
{
|
|
|
|
S_PlaySoundPitch(self, channel | CHAN_LOOP, soundid, (float)volume, (float)attenuation, local, (float)pitch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopSound
|
|
|
|
//
|
|
|
|
// Stops a sound from a single actor from playing on a specific channel.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_StopSound (AActor *actor, int channel)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StopSound(SOURCE_Actor, actor, (i_compatflags & COMPATF_MAGICSILENCE) ? -1 : channel);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopSound
|
|
|
|
//
|
|
|
|
// Stops a sound from a single sector from playing on a specific channel.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_StopSound (const sector_t *sec, int channel)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StopSound(SOURCE_Sector, sec, (i_compatflags & COMPATF_MAGICSILENCE) ? -1 : channel);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_StopSound
|
|
|
|
//
|
|
|
|
// Stops a sound from a single polyobject from playing on a specific channel.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_StopSound (const FPolyObj *poly, int channel)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StopSound(SOURCE_Polyobj, poly, (i_compatflags & COMPATF_MAGICSILENCE) ? -1 : channel);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_RelinkSound
|
|
|
|
//
|
|
|
|
// Moves all the sounds from one thing to another. If the destination is
|
2019-12-08 20:22:53 +00:00
|
|
|
// nullptr, then the sound becomes a positioned sound.
|
2019-12-08 12:28:52 +00:00
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_RelinkSound (AActor *from, AActor *to)
|
|
|
|
{
|
|
|
|
|
|
|
|
FVector3 p = from->SoundPos();
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->RelinkSound(SOURCE_Actor, from, to, !(compatflags2 & COMPATF2_SOUNDCUTOFF)? &p : nullptr);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ChangeSoundVolume
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_ChangeActorSoundVolume(AActor *actor, int channel, double dvolume)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->ChangeSoundVolume(SOURCE_Actor, actor, (i_compatflags & COMPATF_MAGICSILENCE)? -1 : channel, dvolume);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ChangeSoundPitch
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_ChangeActorSoundPitch(AActor *actor, int channel, double pitch)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->ChangeSoundPitch(SOURCE_Actor, actor, channel, pitch);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_GetSoundPlayingInfo
|
|
|
|
//
|
|
|
|
// Is a sound being played by a specific emitter?
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool S_GetSoundPlayingInfo (const AActor *actor, int sound_id)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
return soundEngine->GetSoundPlayingInfo(SOURCE_Actor, actor, sound_id);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool S_GetSoundPlayingInfo (const sector_t *sec, int sound_id)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
return soundEngine->GetSoundPlayingInfo(SOURCE_Sector, sec, sound_id);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool S_GetSoundPlayingInfo (const FPolyObj *poly, int sound_id)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
return soundEngine->GetSoundPlayingInfo(SOURCE_Polyobj, poly, sound_id);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_IsActorPlayingSomething
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool S_IsActorPlayingSomething (AActor *actor, int channel, int sound_id)
|
|
|
|
{
|
|
|
|
if (i_compatflags & COMPATF_MAGICSILENCE)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
channel = CHAN_AUTO; // checks all channels
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
2019-12-08 20:22:53 +00:00
|
|
|
return soundEngine->IsSourcePlayingSomething(SOURCE_Actor, actor, channel, sound_id);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Sets the internal listener structure
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void S_SetListener(AActor *listenactor)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
SoundListener listener;
|
|
|
|
if (listenactor != nullptr)
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
listener.angle = (float)listenactor->Angles.Yaw.Radians();
|
|
|
|
/*
|
|
|
|
listener.velocity.X = listenactor->vel.x * (TICRATE/65536.f);
|
|
|
|
listener.velocity.Y = listenactor->vel.z * (TICRATE/65536.f);
|
|
|
|
listener.velocity.Z = listenactor->vel.y * (TICRATE/65536.f);
|
|
|
|
*/
|
|
|
|
listener.velocity.Zero();
|
|
|
|
listener.position = listenactor->SoundPos();
|
|
|
|
listener.underwater = listenactor->waterlevel == 3;
|
|
|
|
assert(level.Zones.Size() > listenactor->Sector->ZoneNumber);
|
|
|
|
listener.Environment = level.Zones[listenactor->Sector->ZoneNumber].Environment;
|
|
|
|
listener.valid = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
listener.angle = 0;
|
|
|
|
listener.position.Zero();
|
|
|
|
listener.velocity.Zero();
|
|
|
|
listener.underwater = false;
|
2019-12-08 20:22:53 +00:00
|
|
|
listener.Environment = nullptr;
|
2019-12-08 12:28:52 +00:00
|
|
|
listener.valid = false;
|
|
|
|
}
|
|
|
|
listener.ListenerObject = listenactor;
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->SetListener(listener);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_UpdateSounds
|
|
|
|
//
|
|
|
|
// Updates music & sounds
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_UpdateSounds (AActor *listenactor)
|
|
|
|
{
|
|
|
|
// should never happen
|
|
|
|
S_SetListener(listenactor);
|
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
SN_UpdateActiveSequences();
|
2019-12-08 12:28:52 +00:00
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->UpdateSounds(level.time);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Although saving the sound system's state is supposed to be an engine
|
|
|
|
// feature, the specifics cannot be set up there, this needs to be on the client side.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static FSerializer &Serialize(FSerializer &arc, const char *key, FSoundChan &chan, FSoundChan *def)
|
|
|
|
{
|
|
|
|
if (arc.BeginObject(key))
|
|
|
|
{
|
|
|
|
arc("sourcetype", chan.SourceType)
|
|
|
|
("soundid", chan.SoundID)
|
|
|
|
("orgid", chan.OrgID)
|
|
|
|
("volume", chan.Volume)
|
|
|
|
("distancescale", chan.DistanceScale)
|
|
|
|
("pitch", chan.Pitch)
|
|
|
|
("chanflags", chan.ChanFlags)
|
|
|
|
("entchannel", chan.EntChannel)
|
|
|
|
("priority", chan.Priority)
|
|
|
|
("nearlimit", chan.NearLimit)
|
|
|
|
("starttime", chan.StartTime)
|
|
|
|
("rolloftype", chan.Rolloff.RolloffType)
|
|
|
|
("rolloffmin", chan.Rolloff.MinDistance)
|
|
|
|
("rolloffmax", chan.Rolloff.MaxDistance)
|
|
|
|
("limitrange", chan.LimitRange);
|
|
|
|
|
|
|
|
switch (chan.SourceType)
|
|
|
|
{
|
|
|
|
case SOURCE_None: break;
|
|
|
|
case SOURCE_Actor: { AActor* s = (AActor*)chan.Source; arc("actor", s); chan.Source = s; break; }
|
|
|
|
case SOURCE_Sector: { auto s = (sector_t*)chan.Source; arc("sector", s); chan.Source = s; break; }
|
|
|
|
case SOURCE_Polyobj: { auto s = (FPolyObj*)chan.Source; arc("poly", s); chan.Source = s; break; }
|
|
|
|
case SOURCE_Unattached: arc.Array("point", chan.Point, 3); break;
|
|
|
|
default: I_Error("Unknown sound source type %d\n", chan.SourceType); break;
|
|
|
|
}
|
|
|
|
arc.EndObject();
|
|
|
|
}
|
|
|
|
return arc;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_SerializeSounds
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_SerializeSounds(FSerializer &arc)
|
|
|
|
{
|
|
|
|
FSoundChan *chan;
|
|
|
|
|
|
|
|
GSnd->Sync(true);
|
|
|
|
|
|
|
|
if (arc.isWriting())
|
|
|
|
{
|
|
|
|
// Count channels and accumulate them so we can store them in
|
|
|
|
// reverse order. That way, they will be in the same order when
|
|
|
|
// reloaded later as they are now.
|
2019-12-08 20:22:53 +00:00
|
|
|
TArray<FSoundChan*> chans = soundEngine->AllActiveChannels();
|
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
if (chans.Size() > 0 && arc.BeginArray("sounds"))
|
|
|
|
{
|
|
|
|
for (unsigned int i = chans.Size(); i-- != 0; )
|
|
|
|
{
|
|
|
|
// Replace start time with sample position.
|
|
|
|
uint64_t start = chans[i]->StartTime;
|
|
|
|
chans[i]->StartTime = GSnd ? GSnd->GetPosition(chans[i]) : 0;
|
|
|
|
arc(nullptr, *chans[i]);
|
|
|
|
chans[i]->StartTime = start;
|
|
|
|
}
|
|
|
|
arc.EndArray();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned int count;
|
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->StopAllChannels();
|
2019-12-08 12:28:52 +00:00
|
|
|
if (arc.BeginArray("sounds"))
|
|
|
|
{
|
|
|
|
count = arc.ArraySize();
|
|
|
|
for (unsigned int i = 0; i < count; ++i)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
chan = (FSoundChan*)soundEngine->GetChannel(nullptr);
|
2019-12-08 12:28:52 +00:00
|
|
|
arc(nullptr, *chan);
|
|
|
|
// Sounds always start out evicted when restored from a save.
|
|
|
|
chan->ChanFlags |= CHAN_EVICTED | CHAN_ABSTIME;
|
|
|
|
}
|
|
|
|
arc.EndArray();
|
|
|
|
}
|
|
|
|
// The two tic delay is to make sure any screenwipes have finished.
|
|
|
|
// This needs to be two because the game is run for one tic before
|
|
|
|
// the wipe so that it can produce a screen to wipe to. So if we
|
|
|
|
// only waited one tic to restart the sounds, they would start
|
|
|
|
// playing before the wipe, and depending on the synchronization
|
|
|
|
// between the main thread and the mixer thread at the time, the
|
|
|
|
// sounds might be heard briefly before pausing for the wipe.
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->SetRestartTime(level.time + 2);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
GSnd->Sync(false);
|
|
|
|
GSnd->UpdateSounds();
|
|
|
|
}
|
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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 != nullptr)
|
|
|
|
{
|
|
|
|
GSnd->SetInactive(SoundRenderer::INACTIVE_Active);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (paused == 0)
|
|
|
|
{
|
|
|
|
S_PauseSound(false, true);
|
|
|
|
if (GSnd != nullptr)
|
|
|
|
{
|
|
|
|
GSnd->SetInactive(gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL ?
|
|
|
|
SoundRenderer::INACTIVE_Complete :
|
|
|
|
SoundRenderer::INACTIVE_Mute);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!netgame
|
|
|
|
#ifdef _DEBUG
|
|
|
|
&& !demoplayback
|
|
|
|
#endif
|
|
|
|
)
|
|
|
|
{
|
|
|
|
pauseext = !state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void CalcSectorSoundOrg(const DVector3& listenpos, const sector_t* sec, int channum, FVector3& pos)
|
|
|
|
{
|
|
|
|
if (!(i_compatflags & COMPATF_SECTORSOUNDS))
|
|
|
|
{
|
|
|
|
// Are we inside the sector? If yes, the closest point is the one we're on.
|
|
|
|
if (P_PointInSector(listenpos.X, listenpos.Y) == sec)
|
|
|
|
{
|
|
|
|
pos.X = (float)listenpos.X;
|
|
|
|
pos.Z = (float)listenpos.Y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Find the closest point on the sector's boundary lines and use
|
|
|
|
// that as the perceived origin of the sound.
|
|
|
|
DVector2 xy;
|
|
|
|
sec->ClosestPoint(listenpos, xy);
|
|
|
|
pos.X = (float)xy.X;
|
|
|
|
pos.Z = (float)xy.Y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pos.X = float(sec->centerspot.X);
|
|
|
|
pos.Z = float(sec->centerspot.Y);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set sound vertical position based on channel.
|
|
|
|
if (channum == CHAN_FLOOR)
|
|
|
|
{
|
|
|
|
pos.Y = (float)MIN<double>(sec->floorplane.ZatPoint(listenpos), listenpos.Z);
|
|
|
|
}
|
|
|
|
else if (channum == CHAN_CEILING)
|
|
|
|
{
|
|
|
|
pos.Y = (float)MAX<double>(sec->ceilingplane.ZatPoint(listenpos), listenpos.Z);
|
|
|
|
}
|
|
|
|
else if (channum == CHAN_INTERIOR)
|
|
|
|
{
|
|
|
|
pos.Y = (float)clamp<double>(listenpos.Z, sec->floorplane.ZatPoint(listenpos), sec->ceilingplane.ZatPoint(listenpos));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void CalcPolyobjSoundOrg(const DVector3& listenpos, const FPolyObj* poly, FVector3& pos)
|
|
|
|
{
|
|
|
|
side_t* side;
|
|
|
|
sector_t* sec;
|
|
|
|
|
|
|
|
DVector2 ppos;
|
|
|
|
poly->ClosestPoint(listenpos, ppos, &side);
|
|
|
|
pos.X = (float)ppos.X;
|
|
|
|
pos.Z = (float)ppos.Y;
|
|
|
|
sec = side->sector;
|
|
|
|
pos.Y = (float)clamp<double>(listenpos.Z, sec->floorplane.ZatPoint(listenpos), sec->ceilingplane.ZatPoint(listenpos));
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================
|
|
|
|
//
|
|
|
|
// CalcPosVel
|
|
|
|
//
|
|
|
|
// The game specific part of the sound updater.
|
|
|
|
//
|
|
|
|
//=========================================================================
|
|
|
|
|
|
|
|
void DoomSoundEngine::CalcPosVel(int type, const void* source, const float pt[3], int channum, int chanflags, FVector3* pos, FVector3* vel)
|
|
|
|
{
|
|
|
|
if (pos != nullptr)
|
|
|
|
{
|
|
|
|
DVector3 listenpos;
|
|
|
|
int pgroup;
|
|
|
|
AActor* listener = players[consoleplayer].camera;
|
|
|
|
|
|
|
|
if (listener != nullptr)
|
|
|
|
{
|
|
|
|
listenpos = listener->Pos();
|
|
|
|
*pos = listener->SoundPos();
|
|
|
|
pgroup = listener->Sector->PortalGroup;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
listenpos.Zero();
|
|
|
|
pos->Zero();
|
|
|
|
pgroup = 0;
|
|
|
|
}
|
|
|
|
if (vel) vel->Zero();
|
|
|
|
|
|
|
|
// [BL] Moved this case out of the switch statement to make code easier
|
|
|
|
// on static analysis.
|
|
|
|
if (type == SOURCE_Unattached)
|
|
|
|
{
|
|
|
|
sector_t* sec = P_PointInSector(pt[0], pt[2]);
|
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, sec->PortalGroup);
|
|
|
|
pos->X = pt[0] - (float)disp.X;
|
|
|
|
pos->Y = !(chanflags & CHAN_LISTENERZ) ? pt[1] : (float)listenpos.Z;
|
|
|
|
pos->Z = pt[2] - (float)disp.Y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case SOURCE_None:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SOURCE_Actor:
|
|
|
|
{
|
|
|
|
auto actor = (AActor*)source;
|
|
|
|
//assert(actor != nullptr);
|
|
|
|
if (actor != nullptr)
|
|
|
|
{
|
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, actor->Sector->PortalGroup);
|
|
|
|
DVector3 posi = actor->Pos() - disp;
|
|
|
|
*pos = { (float)posi.X, (float)posi.Z, (float)posi.Y };
|
|
|
|
if (vel)
|
|
|
|
{
|
|
|
|
vel->X = float(actor->Vel.X * TICRATE);
|
|
|
|
vel->Y = float(actor->Vel.Z * TICRATE);
|
|
|
|
vel->Z = float(actor->Vel.Y * TICRATE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SOURCE_Sector:
|
|
|
|
{
|
|
|
|
auto sector = (sector_t*)source;
|
|
|
|
assert(sector != nullptr);
|
|
|
|
if (sector != nullptr)
|
|
|
|
{
|
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, sector->PortalGroup);
|
|
|
|
if (chanflags & CHAN_AREA)
|
|
|
|
{
|
|
|
|
// listener must be reversely offset to calculate the proper sound origin.
|
|
|
|
CalcSectorSoundOrg(listenpos + disp, sector, channum, *pos);
|
|
|
|
pos->X -= (float)disp.X;
|
|
|
|
pos->Z -= (float)disp.Y;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
pos->X = (float)(sector->centerspot.X - disp.X);
|
|
|
|
pos->Z = (float)(sector->centerspot.Y - disp.Y);
|
|
|
|
chanflags |= CHAN_LISTENERZ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case SOURCE_Polyobj:
|
|
|
|
{
|
|
|
|
auto poly = (FPolyObj*)source;
|
|
|
|
assert(poly != nullptr);
|
|
|
|
if (poly != nullptr)
|
|
|
|
{
|
|
|
|
DVector2 disp = Displacements.getOffset(pgroup, poly->CenterSubsector->sector->PortalGroup);
|
|
|
|
CalcPolyobjSoundOrg(listenpos + disp, poly, *pos);
|
|
|
|
pos->X -= (float)disp.X;
|
|
|
|
pos->Z -= (float)disp.Y;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((chanflags & CHAN_LISTENERZ) && players[consoleplayer].camera != nullptr)
|
|
|
|
{
|
|
|
|
pos->Y = (float)listenpos.Z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ValidatePosVel
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
inline bool Validate(const float value, const float limit)
|
|
|
|
{
|
|
|
|
return value >= -limit && value <= limit;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool Validate(const FVector3& value, const float limit, const char* const name, const AActor* const actor)
|
|
|
|
{
|
|
|
|
const bool valid =
|
|
|
|
Validate(value.X, limit)
|
|
|
|
&& Validate(value.Y, limit)
|
|
|
|
&& Validate(value.Z, limit);
|
|
|
|
|
|
|
|
if (!valid)
|
|
|
|
{
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
if (actor == nullptr)
|
|
|
|
{
|
|
|
|
Printf("\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf(TEXTCOLOR_RED " for actor of class " TEXTCOLOR_WHITE "%s\n", actor->GetClass()->TypeName.GetChars());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DoomSoundEngine::ValidatePosVel(int sourcetype, const void* source, const FVector3& pos, const FVector3& vel)
|
|
|
|
{
|
|
|
|
if (sourcetype != SOURCE_Actor) return true;
|
|
|
|
// The actual limit for map coordinates
|
|
|
|
auto actor = (AActor*)source;
|
|
|
|
static const float POSITION_LIMIT = 1024.f * 1024.f;
|
|
|
|
const bool valid = Validate(pos, POSITION_LIMIT, "position", actor);
|
|
|
|
|
|
|
|
// The maximum velocity is enough to travel through entire map in one tic
|
|
|
|
static const float VELOCITY_LIMIT = 2 * POSITION_LIMIT * TICRATE;
|
|
|
|
return Validate(vel, VELOCITY_LIMIT, "velocity", actor) && valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// This is to avoid hardscoding the dependency on Wads into the sound engine
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
TArray<uint8_t> DoomSoundEngine::ReadSound(int lumpnum)
|
|
|
|
{
|
|
|
|
auto wlump = Wads.OpenLumpReader(lumpnum);
|
|
|
|
return wlump.Read();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_PickReplacement
|
|
|
|
//
|
|
|
|
// This is overridden to use a synchronized RNG.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
static FRandom pr_randsound("RandSound");
|
|
|
|
|
|
|
|
int DoomSoundEngine::PickReplacement(int refid)
|
|
|
|
{
|
|
|
|
while (S_sfx[refid].bRandomHeader)
|
|
|
|
{
|
|
|
|
const FRandomSoundList* list = &S_rnd[S_sfx[refid].link];
|
|
|
|
refid = list->Choices[pr_randsound(list->Choices.Size())];
|
|
|
|
}
|
|
|
|
return refid;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_NoiseDebug
|
|
|
|
//
|
|
|
|
// [RH] Print sound debug info. Called by status bar.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void DoomSoundEngine::NoiseDebug()
|
|
|
|
{
|
|
|
|
FSoundChan* chan;
|
|
|
|
FVector3 listener;
|
|
|
|
FVector3 origin;
|
|
|
|
int y, color;
|
|
|
|
|
|
|
|
y = 32 * CleanYfac;
|
|
|
|
screen->DrawText(NewConsoleFont, 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 == nullptr)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
listener = players[consoleplayer].camera->SoundPos();
|
|
|
|
|
|
|
|
// Display the oldest channel first.
|
|
|
|
for (chan = Channels; chan->NextChan != nullptr; chan = chan->NextChan)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
while (y < SCREENHEIGHT - 16)
|
|
|
|
{
|
|
|
|
char temp[32];
|
|
|
|
|
|
|
|
SoundEngine::CalcPosVel(chan, &origin, nullptr);
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
void S_NoiseDebug(void)
|
|
|
|
{
|
|
|
|
static_cast<DoomSoundEngine*>(soundEngine)->NoiseDebug();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CCMD soundlist
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void DoomSoundEngine::PrintSoundList()
|
|
|
|
{
|
|
|
|
auto &S_sfx = soundEngine->GetSounds();
|
|
|
|
char lumpname[9];
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
lumpname[8] = 0;
|
|
|
|
for (i = 0; i < S_sfx.Size(); i++)
|
|
|
|
{
|
|
|
|
const sfxinfo_t* sfx = &S_sfx[i];
|
|
|
|
if (sfx->bRandomHeader)
|
|
|
|
{
|
|
|
|
Printf("%3d. %s -> #%d {", i, sfx->name.GetChars(), sfx->link);
|
|
|
|
const FRandomSoundList* list = &S_rnd[sfx->link];
|
|
|
|
for (auto& me : list->Choices)
|
|
|
|
{
|
|
|
|
Printf(" %s ", S_sfx[me].name.GetChars());
|
|
|
|
}
|
|
|
|
Printf("}\n");
|
|
|
|
}
|
|
|
|
else if (sfx->bPlayerReserve)
|
|
|
|
{
|
|
|
|
Printf("%3d. %s <<player sound %d>>\n", i, sfx->name.GetChars(), sfx->link);
|
|
|
|
}
|
|
|
|
else if (S_sfx[i].lumpnum != -1)
|
|
|
|
{
|
|
|
|
Wads.GetLumpName(lumpname, sfx->lumpnum);
|
|
|
|
Printf("%3d. %s (%s)\n", i, sfx->name.GetChars(), lumpname);
|
|
|
|
}
|
|
|
|
else if (S_sfx[i].link != sfxinfo_t::NO_LINK)
|
|
|
|
{
|
|
|
|
Printf("%3d. %s -> %s\n", i, sfx->name.GetChars(), S_sfx[sfx->link].name.GetChars());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Printf("%3d. %s **not present**\n", i, sfx->name.GetChars());
|
|
|
|
}
|
|
|
|
Printf(" PitchMask = %d\n", sfx->PitchMask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD(soundlist)
|
|
|
|
{
|
|
|
|
static_cast<DoomSoundEngine*>(soundEngine)->PrintSoundList();
|
|
|
|
}
|
2019-12-08 12:28:52 +00:00
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CCMD playsound
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
CCMD (playsound)
|
|
|
|
{
|
|
|
|
if (argv.argc() > 1)
|
|
|
|
{
|
|
|
|
FSoundID id = argv[1];
|
|
|
|
if (id == 0)
|
|
|
|
{
|
|
|
|
Printf("'%s' is not a sound\n", argv[1]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
S_Sound (CHAN_AUTO | CHAN_UI, id, 1.f, ATTN_NONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CCMD loopsound
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
CCMD (loopsound)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
if (players[consoleplayer].mo != nullptr && !netgame && argv.argc() > 1)
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
FSoundID id = argv[1];
|
|
|
|
if (id == 0)
|
|
|
|
{
|
|
|
|
Printf("'%s' is not a sound\n", argv[1]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
AActor *icon = Spawn("SpeakerIcon", players[consoleplayer].mo->PosPlusZ(32.), ALLOW_REPLACE);
|
2019-12-08 20:22:53 +00:00
|
|
|
if (icon != nullptr)
|
2019-12-08 12:28:52 +00:00
|
|
|
{
|
|
|
|
S_Sound(icon, CHAN_BODY | CHAN_LOOP, id, 1.f, ATTN_IDLE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CCMD cachesound <sound name>
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
CCMD (cachesound)
|
|
|
|
{
|
|
|
|
if (argv.argc() < 2)
|
|
|
|
{
|
|
|
|
Printf ("Usage: cachesound <sound> ...\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (int i = 1; i < argv.argc(); ++i)
|
|
|
|
{
|
|
|
|
FSoundID sfxnum = argv[i];
|
|
|
|
if (sfxnum != FSoundID(0))
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->CacheSound(sfxnum);
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CCMD(listsoundchannels)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
Printf("%s", soundEngine->ListSoundChannels().GetChars());
|
2019-12-08 12:28:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// intentionally moved here to keep the s_music include out of the rest of the file.
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_PauseSound
|
|
|
|
//
|
|
|
|
// Stop music and sound effects, during game PAUSE.
|
|
|
|
//==========================================================================
|
|
|
|
#include "s_music.h"
|
|
|
|
|
|
|
|
void S_PauseSound (bool notmusic, bool notsfx)
|
|
|
|
{
|
|
|
|
if (!notmusic)
|
|
|
|
{
|
|
|
|
S_PauseMusic();
|
|
|
|
}
|
|
|
|
if (!notsfx)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->SetPaused(true);
|
2019-12-08 12:28:52 +00:00
|
|
|
GSnd->SetSfxPaused (true, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(DObject, S_PauseSound)
|
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_BOOL(notmusic);
|
|
|
|
PARAM_BOOL(notsfx);
|
|
|
|
S_PauseSound(notmusic, notsfx);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// S_ResumeSound
|
|
|
|
//
|
|
|
|
// Resume music and sound effects, after game PAUSE.
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void S_ResumeSound (bool notsfx)
|
|
|
|
{
|
|
|
|
S_ResumeMusic();
|
|
|
|
if (!notsfx)
|
|
|
|
{
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->SetPaused(false);
|
2019-12-08 12:28:52 +00:00
|
|
|
GSnd->SetSfxPaused (false, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_ACTION_FUNCTION(DObject, S_ResumeSound)
|
|
|
|
{
|
|
|
|
PARAM_PROLOGUE;
|
|
|
|
PARAM_BOOL(notsfx);
|
|
|
|
S_ResumeSound(notsfx);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-12-08 20:22:53 +00:00
|
|
|
|
2019-12-08 12:28:52 +00:00
|
|
|
CCMD (snd_status)
|
|
|
|
{
|
|
|
|
GSnd->PrintStatus ();
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (snd_reset)
|
|
|
|
{
|
|
|
|
S_SoundReset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void S_SoundReset()
|
|
|
|
{
|
|
|
|
S_StopMusic(true);
|
2019-12-08 20:22:53 +00:00
|
|
|
soundEngine->Reset();
|
2019-12-08 12:28:52 +00:00
|
|
|
S_RestartMusic();
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (snd_listdrivers)
|
|
|
|
{
|
|
|
|
GSnd->PrintDriversList ();
|
|
|
|
}
|
|
|
|
|
|
|
|
ADD_STAT (sound)
|
|
|
|
{
|
|
|
|
return GSnd->GatherStats ();
|
|
|
|
}
|