raze/source/core/raze_sound.cpp
2023-12-21 00:50:53 +01:00

351 lines
11 KiB
C++

/*
** s_sound.cpp
** Main sound engine
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2002-2019 Christoph Oelckers
** All rights reserved.
**
** 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 "raze_sound.h"
#include "c_cvars.h"
#include "basics.h"
#include "stats.h"
#include "v_text.h"
#include "filesystem.h"
#include "serializer.h"
#include "name.h"
#include "cmdlib.h"
#include "gamecontrol.h"
#include "build.h"
#include "coreactor.h"
#include "g_input.h"
extern ReverbContainer* ForcedEnvironment;
static int LastReverb;
CUSTOM_CVAR(Bool, snd_reverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
S_SetReverb(-1);
}
// This is for testing reverb settings.
CUSTOM_CVAR(Int, snd_reverbtype, -1, 0)
{
S_SetReverb(-1);
}
void S_SetReverb(int strength)
{
if (strength == -1) strength = LastReverb;
if (snd_reverbtype > -1)
{
strength = snd_reverbtype;
ForcedEnvironment = S_FindEnvironment(strength);
}
else if (snd_reverb && strength > 0)
{
// todo: optimize environments. The original "reverb" was garbage and not usable as reference.
if (strength < 64) strength = 0x1400;
else if (strength < 180) strength = 0x1503;
else if (strength < 220) strength = 0x1502;
else strength = 0x1900;
LastReverb = strength;
ForcedEnvironment = S_FindEnvironment(strength);
}
else ForcedEnvironment = nullptr;
}
//==========================================================================
//
//
//
//==========================================================================
TArray<uint8_t> RazeSoundEngine::ReadSound(int lumpnum)
{
auto wlump = fileSystem.OpenFileReader(lumpnum);
TArray<uint8_t> buffer(wlump.GetLength(), true);
auto len = wlump.Read(buffer.data(), buffer.size());
buffer.Resize((unsigned)len);
return buffer;
}
//==========================================================================
//
// S_NoiseDebug
//
// [RH] Print sound debug info. Called by status bar.
//==========================================================================
FString NoiseDebug(SoundEngine *engine)
{
FVector3 listener;
FVector3 origin;
listener = engine->GetListener().position;
int ch = 0;
FString out;
out.Format("*** SOUND DEBUG INFO ***\nListener: %3.2f %2.3f %2.3f\n"
"x y z vol dist chan pri flags aud pos name\n", listener.X, listener.Y, listener.Z);
for (auto chan = engine->GetChannels(); chan; chan = chan->NextChan)
{
if (!(chan->ChanFlags & CHANF_IS3D))
{
out += "--- --- --- --- ";
}
else
{
engine->CalcPosVel(chan, &origin, nullptr);
out.AppendFormat(TEXTCOLOR_GOLD "%5.0f | %5.0f | %5.0f | %5.0f ", origin.X, origin.Z, origin.Y, (origin - listener).Length());
}
out.AppendFormat("%-.2g %-4d %-4d %sF%s3%sZ%sU%sM%sN%sA%sL%sE%sV" TEXTCOLOR_GOLD " %-5.4f %-4u %d: %s %p\n", chan->Volume, chan->EntChannel, chan->Priority,
(chan->ChanFlags & CHANF_FORGETTABLE) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_IS3D) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_LISTENERZ) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_UI) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_MAYBE_LOCAL) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_NOPAUSE) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_AREA) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_LOOP) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_EVICTED) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
(chan->ChanFlags & CHANF_VIRTUAL) ? TEXTCOLOR_GREEN : TEXTCOLOR_DARKRED,
GSnd->GetAudibility(chan), GSnd->GetPosition(chan), ((int)chan->OrgID.index())-1, engine->GetSfx(chan->SoundID)->name.GetChars(), chan->Source);
ch++;
}
out.AppendFormat("%d channels\n", ch);
return out;
}
ADD_STAT(sounddebug)
{
return NoiseDebug(soundEngine);
}
CVAR(Bool, snd_extendedlookup, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
int S_LookupSound(const char* fn)
{
static const char * const sndformats[] = { "OGG", "FLAC", "WAV" };
if (snd_extendedlookup)
{
auto newfn = StripExtension(fn);
int lump = fileSystem.FindFileWithExtensions(newfn.GetChars(), sndformats, countof(sndformats));
if (lump >= 0) return lump;
newfn = "sound/" + newfn;
lump = fileSystem.FindFileWithExtensions(newfn.GetChars(), sndformats, countof(sndformats));
if (lump >= 0) return lump;
}
return fileSystem.FindFile(fn);
}
//==========================================================================
//
// 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)
("userdata", chan.UserData)
.Array("point", chan.Point, 3);
assert(dynamic_cast<RazeSoundEngine*>(soundEngine));
auto eng = static_cast<RazeSoundEngine*>(soundEngine);
// Let's handle actor sources here becaue they are the same for all games.
if (eng->SourceIsActor(&chan))
{
DCoreActor* SourceIndex = nullptr;
if (arc.isWriting()) SourceIndex = const_cast<DCoreActor*>(reinterpret_cast<const DCoreActor*>(chan.Source));
arc("Source", SourceIndex);
if (arc.isReading()) chan.Source = SourceIndex;
}
else
{
int SourceIndex = 0;
if (arc.isWriting()) SourceIndex = eng->SoundSourceIndex(&chan);
arc("Source", SourceIndex);
if (arc.isReading()) eng->SetSource(&chan, SourceIndex);
}
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.
TArray<FSoundChan*> chans = soundEngine->AllActiveChannels();
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;
soundEngine->StopAllChannels();
if (arc.BeginArray("sounds"))
{
count = arc.ArraySize();
for (unsigned int i = 0; i < count; ++i)
{
chan = (FSoundChan*)soundEngine->GetChannel(nullptr);
arc(nullptr, *chan);
// Sounds always start out evicted when restored from a save.
chan->ChanFlags |= CHANF_EVICTED | CHANF_ABSTIME;
}
arc.EndArray();
}
// Add a small delay so that eviction only runs once the game is up and runnnig.
soundEngine->SetRestartTime(I_GetTime() + 2);
}
// Check if there's actor sounds without an actor. This can happen if a savegame is badly timed with a freshly destroyed actor.
soundEngine->EnumerateChannels([](FSoundChan* chan)
{
auto eng = static_cast<RazeSoundEngine*>(soundEngine);
if (eng->SourceIsActor(chan) && chan->Source == nullptr)
{
eng->StopChannel(chan);
}
return 0;
});
GSnd->Sync(false);
GSnd->UpdateSounds();
}
//==========================================================================
//
//
//
//==========================================================================
void S_CacheAllSounds(void)
{
for (unsigned i = 0; i < soundEngine->GetNumSounds(); i++)
{
soundEngine->CacheSound(FSoundID::fromInt(i));
if ((i & 31) == 0)
I_GetEvent();
}
}
//==========================================================================
//
// CCMD playsound
//
//==========================================================================
CCMD(playsound)
{
if (argv.argc() > 1)
{
FSoundID id = S_FindSound(argv[1]);
if (id == NO_SOUND)
{
Printf("'%s' is not a sound\n", argv[1]);
}
else
{
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI | CHANF_NOPAUSE, id, 1.f, ATTN_NORM);
}
}
}
//==========================================================================
//
// CCMD playsound
//
//==========================================================================
CCMD(playsoundid)
{
if (argv.argc() > 1)
{
FSoundID id = soundEngine->FindSoundByResID((int)strtol(argv[1], nullptr, 0));
if (id == NO_SOUND)
{
Printf("'%s' is not a sound\n", argv[1]);
}
else
{
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI | CHANF_NOPAUSE, id, 1.f, ATTN_NORM);
}
}
}