mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-29 23:42:08 +00:00
351 lines
11 KiB
C++
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(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);
|
|
}
|
|
}
|
|
}
|
|
|