Files
DoomRTX/neo/engine/sound/snd_shader.cpp
T
2026-05-09 22:10:40 -07:00

813 lines
18 KiB
C++

/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
IceTech Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with IceTech Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#include "snd_local.h"
/*
===============
idSoundShader::Init
===============
*/
void idSoundShader::Init( void ) {
desc = "<no description>";
errorDuringParse = false;
onDemand = false;
numEntries = 0;
numLeadins = 0;
leadinVolume = 0;
altSound = NULL;
}
/*
===============
idSoundShader::idSoundShader
===============
*/
idSoundShader::idSoundShader( void ) {
Init();
}
/*
===============
idSoundShader::~idSoundShader
===============
*/
idSoundShader::~idSoundShader( void ) {
}
/*
=================
idSoundShader::Size
=================
*/
size_t idSoundShader::Size( void ) const {
return sizeof( idSoundShader );
}
/*
===============
idSoundShader::idSoundShader::FreeData
===============
*/
void idSoundShader::FreeData() {
numEntries = 0;
numLeadins = 0;
}
/*
===================
idSoundShader::SetDefaultText
===================
*/
bool idSoundShader::SetDefaultText( void ) {
idStr wavname;
wavname = GetName();
wavname.DefaultFileExtension( ".wav" ); // if the name has .ogg in it, that will stay
// if there exists a wav file with the same name
if ( 1 ) { //fileSystem->ReadFile( wavname, NULL ) != -1 ) {
char generated[2048];
idStr::snPrintf( generated, sizeof( generated ),
"sound %s // IMPLICITLY GENERATED\n"
"{\n"
"%s\n"
"}\n", GetName(), wavname.c_str() );
SetText( generated );
return true;
} else {
return false;
}
}
/*
===================
DefaultDefinition
===================
*/
const char *idSoundShader::DefaultDefinition() const {
return
"{\n"
"\t" "_default.wav\n"
"}";
}
/*
===============
idSoundShader::Parse
this is called by the declManager
===============
*/
bool idSoundShader::Parse( const char *text, const int textLength ) {
idLexer src;
src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
src.SetFlags( DECL_LEXER_FLAGS );
src.SkipUntilString( "{" );
// deeper functions can set this, which will cause MakeDefault() to be called at the end
errorDuringParse = false;
if ( !ParseShader( src ) || errorDuringParse ) {
MakeDefault();
return false;
}
return true;
}
/*
===============
idSoundShader::ParseShader
===============
*/
/*
===============
idSoundShader::ParseShader
===============
*/
bool idSoundShader::ParseShader(idLexer& src) {
int i;
idToken token;
#ifdef QUAKE4
parms.minDistance = 40.0f;
parms.maxDistance = 400.0f;
parms.volume = 1.0f;
parms.attenuatedVolume = 0.0f;
parms.shakes = 0.0f;
parms.soundShaderFlags = 0;
parms.soundClass = 0;
parms.frequencyShift = 1.0f;
parms.wetLevel = 0.0f;
parms.dryLevel = 0.0f;
minFrequencyShift = 1.0f;
maxFrequencyShift = 1.0f;
noShakes = false;
frequentlyUsed = false;
#else
parms.minDistance = 1;
parms.maxDistance = 10;
parms.volume = 1;
parms.shakes = 0;
parms.soundShaderFlags = 0;
parms.soundClass = 0;
#endif
#ifdef PREY
parms.subIndex = 0;
parms.profanityIndex = -1;
parms.profanityDelay = 0.0f;
parms.profanityDuration = 0.0f;
#endif
speakerMask = 0;
altSound = NULL;
for (i = 0; i < SOUND_MAX_LIST_WAVS; i++) {
leadins[i] = NULL;
entries[i] = NULL;
}
numEntries = 0;
numLeadins = 0;
int maxSamples = idSoundSystemLocal::s_maxSoundsPerShader.GetInteger();
if (com_makingBuild.GetBool() || maxSamples <= 0 || maxSamples > SOUND_MAX_LIST_WAVS) {
maxSamples = SOUND_MAX_LIST_WAVS;
}
while (1) {
if (!src.ExpectAnyToken(&token)) {
return false;
}
// end of definition
if (token == "}") {
break;
}
// minimum number of sounds
else if (!token.Icmp("minSamples")) {
#ifdef QUAKE4
const int requestedSamples = src.ParseInt();
if (maxSamples < requestedSamples) {
maxSamples = requestedSamples;
}
if (maxSamples > SOUND_MAX_LIST_WAVS) {
maxSamples = SOUND_MAX_LIST_WAVS;
}
#else
maxSamples = idMath::ClampInt(src.ParseInt(), SOUND_MAX_LIST_WAVS, maxSamples);
#endif
}
#ifdef QUAKE4
// Raven: frequency shift range
else if (!token.Icmp("frequencyshift")) {
minFrequencyShift = src.ParseFloat();
if (!src.ExpectTokenString(",")) {
src.FreeSource();
return false;
}
maxFrequencyShift = src.ParseFloat();
parms.frequencyShift = minFrequencyShift;
}
#endif
// description
else if (!token.Icmp("description")) {
src.ReadTokenOnLine(&token);
desc = token.c_str();
}
// mindistance
else if (!token.Icmp("mindistance")) {
parms.minDistance = src.ParseFloat();
}
// maxdistance
else if (!token.Icmp("maxdistance")) {
parms.maxDistance = src.ParseFloat();
}
// shakes screen
else if (!token.Icmp("shakes")) {
src.ExpectAnyToken(&token);
if (token.type == TT_NUMBER) {
parms.shakes = token.GetFloatValue();
}
else {
src.UnreadToken(&token);
parms.shakes = 1.0f;
}
}
#ifdef QUAKE4
// Raven: indexed shake amplitude data
else if (!token.Icmp("shakeData")) {
if (!src.ExpectAnyToken(&token)) {
return false;
}
const int shakeIndex = atoi(token.c_str());
if (!src.ExpectAnyToken(&token)) {
return false;
}
//SetShakeData(shakeIndex, token.c_str());
}
#endif
// reverb -- parsed but unsupported
else if (!token.Icmp("reverb")) {
src.ParseFloat();
if (!src.ExpectTokenString(",")) {
src.FreeSource();
return false;
}
src.ParseFloat();
}
// volume
else if (!token.Icmp("volume")) {
parms.volume = src.ParseFloat();
}
#ifdef QUAKE4
// Raven: volume specified in decibels, clamped to +10dB
else if (!token.Icmp("volumeDb")) {
float volumeDb = src.ParseFloat();
if (volumeDb > 10.0f) {
common->Warning("Clamping volume of '%s' to +10dB (3 times louder)", GetName());
volumeDb = 10.0f;
}
parms.volume = idMath::dBToScale(volumeDb);
}
#endif
// leadinVolume is used to allow light breaking leadin sounds to be much louder than the broken loop
else if (!token.Icmp("leadinVolume")) {
leadinVolume = src.ParseFloat();
}
// speaker masks
else if (!token.Icmp("mask_center")) {
speakerMask |= 1 << SPEAKER_CENTER;
}
else if (!token.Icmp("mask_left")) {
speakerMask |= 1 << SPEAKER_LEFT;
}
else if (!token.Icmp("mask_right")) {
speakerMask |= 1 << SPEAKER_RIGHT;
}
else if (!token.Icmp("mask_backright")) {
speakerMask |= 1 << SPEAKER_BACKRIGHT;
}
else if (!token.Icmp("mask_backleft")) {
speakerMask |= 1 << SPEAKER_BACKLEFT;
}
else if (!token.Icmp("mask_lfe")) {
speakerMask |= 1 << SPEAKER_LFE;
}
// soundClass
else if (!token.Icmp("soundClass")) {
#ifdef PREY
if (!src.ReadToken(&token)) {
src.Warning("Expected classname after soundClass");
return false;
}
if (!token.Icmp("SC_NORMAL")) {
parms.soundClass = SOUNDCLASS_NORMAL;
}
else if (!token.Icmp("SC_VOICEDUCKER")) {
parms.soundClass = SOUNDCLASS_VOICEDUCKER;
}
else if (!token.Icmp("SC_SPIRIT")) {
parms.soundClass = SOUNDCLASS_SPIRITWALK;
}
else if (!token.Icmp("SC_VOICE")) {
parms.soundClass = SOUNDCLASS_VOICE;
}
else if (!token.Icmp("SC_MUSIC")) {
parms.soundClass = SOUNDCLASS_MUSIC;
}
else {
src.Warning("SoundClass takes args of SC_NORMAL, SC_SPIRIT, SC_VOICE, SC_VOICEDUCKER");
return false;
}
#else
parms.soundClass = src.ParseInt();
if (parms.soundClass < 0 || parms.soundClass >= SOUND_MAX_CLASSES) {
src.Warning("SoundClass out of range");
return false;
}
#endif
}
// altSound
else if (!token.Icmp("altSound")) {
if (!src.ExpectAnyToken(&token)) {
return false;
}
altSound = declManager->FindSound(token.c_str());
}
// ordered -- unsupported
else if (!token.Icmp("ordered")) {
}
// no_dups
else if (!token.Icmp("no_dups")) {
parms.soundShaderFlags |= SSF_NO_DUPS;
}
// no_flicker
else if (!token.Icmp("no_flicker")) {
parms.soundShaderFlags |= SSF_NO_FLICKER;
}
// plain -- unsupported
else if (!token.Icmp("plain")) {
}
// looping
else if (!token.Icmp("looping")) {
parms.soundShaderFlags |= SSF_LOOPING;
}
// no occlusion
else if (!token.Icmp("no_occlusion")) {
parms.soundShaderFlags |= SSF_NO_OCCLUSION;
}
#ifdef PREY
// HumanHead/Prey: no game portal sound flow
else if (!token.Icmp("noportalflow")) {
#if GAMEPORTAL_SOUND
parms.soundShaderFlags |= SSF_NOPORTALFLOW;
#endif
}
// HumanHead/Prey: no reverb
else if (!token.Icmp("noreverb")) {
parms.soundShaderFlags |= SSF_NOREVERB;
}
// HumanHead/Prey: include this shader in voice amplitude queries
else if (!token.Icmp("jawflap")) {
parms.soundShaderFlags |= SSF_VOICEAMPLITUDE;
}
// HumanHead/Prey: become omni when inside minDistance
else if (!token.Icmp("omniwhenclose")) {
parms.soundShaderFlags |= SSF_OMNI_WHEN_CLOSE;
}
#endif
// private
else if (!token.Icmp("private")) {
parms.soundShaderFlags |= SSF_PRIVATE_SOUND;
}
// antiPrivate
else if (!token.Icmp("antiPrivate")) {
parms.soundShaderFlags |= SSF_ANTI_PRIVATE_SOUND;
}
// once
else if (!token.Icmp("playonce")) {
parms.soundShaderFlags |= SSF_PLAY_ONCE;
}
// global
else if (!token.Icmp("global")) {
parms.soundShaderFlags |= SSF_GLOBAL;
}
// unclamped
else if (!token.Icmp("unclamped")) {
parms.soundShaderFlags |= SSF_UNCLAMPED;
}
// omnidirectional
else if (!token.Icmp("omnidirectional")) {
parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL;
}
#ifdef QUAKE4
// Raven: allow doppler pitch shifting
else if (!token.Icmp("useDoppler")) {
parms.soundShaderFlags |= SSF_USEDOPPLER;
}
// Raven: don't randomly seek into looping sounds
else if (!token.Icmp("noRandomStart")) {
parms.soundShaderFlags |= SSF_NO_RANDOMSTART;
}
// Raven: marks VO directed at player/radio chatter
else if (!token.Icmp("voForPlayer")) {
parms.soundShaderFlags |= SSF_VO_FOR_PLAYER;
}
// Raven: disable generated shake data
else if (!token.Icmp("no_shakes")) {
noShakes = true;
}
// Raven: force expansion / keep hot
else if (!token.Icmp("frequentlyUsed")) {
frequentlyUsed = true;
}
#endif
// onDemand can't be a parms, because we must track all references and overrides would confuse it
else if (!token.Icmp("onDemand")) {
// no longer loading sounds on demand
// onDemand = true;
}
#ifdef PREY
// HumanHead/Prey: profanity bleep data
else if (!token.Icmp("bleep")) {
parms.profanityIndex = src.ParseInt();
parms.profanityDelay = src.ParseFloat();
parms.profanityDuration = src.ParseFloat();
}
// HumanHead/Prey: subtitle / subtitlecombat parser
else if (!token.Icmpn("subtitle", 8)) {
const bool combatSubtitle = (token.Icmpn("subtitlecombat", 14) == 0);
const char* numText;
if (combatSubtitle) {
numText = token.c_str() + 14;
}
else {
numText = token.c_str() + 8;
}
const int subNum = (numText[0] != '\0') ? atoi(numText) : 1;
if (!src.ReadToken(&token)) {
src.Warning("Expected time after subtext");
return false;
}
int subChannel = 0;
if (token.c_str()[0] == 'c' || token.c_str()[0] == 'C') {
const int channelNumber = atoi(token.c_str() + 1);
subChannel = channelNumber - 1;
if (subChannel < 0 || subChannel >= MAX_SUBTITLE_CHANNELS) {
src.Warning("Value '%i' is out of range for MAX_SUBTITLE_CHANNELS.", channelNumber);
return false;
}
if (!src.ReadToken(&token)) {
src.Warning("Expected time after subtitle channel");
return false;
}
}
const float subTime = atof(token.c_str());
if (!src.ReadToken(&token)) {
src.Warning("Expected text after subtext and time");
return false;
}
if (combatSubtitle) {
subChannel += 3;
}
if (parms.subIndex == 0) {
parms.subIndex = soundSystemLocal.GetSubtitleIndex(GetName());
}
soundSystemLocal.SetSubtitleData(parms.subIndex, subNum, token.c_str(), subTime, subChannel);
}
#endif
// the leadin wave files
else if (!token.Icmp("leadin")) {
if (!src.ReadToken(&token)) {
src.Warning("Expected sound after leadin");
return false;
}
#ifdef QUAKE4
if (soundSystemLocal.soundCache && numLeadins < maxSamples) {
leadins[numLeadins] = soundSystem->FindSample(token.c_str());
numLeadins++;
}
#else
if (soundSystemLocal.soundCache && numLeadins < maxSamples) {
#ifdef PREY
leadins[numLeadins] = soundSystemLocal.soundCache->FindSound(token.c_str(), onDemand);
#else
leadins[numLeadins] = soundSystemLocal.soundCache->FindSound(token.c_str(), onDemand);
#endif
numLeadins++;
}
#endif
}
// the regular wave files
else if (token.Find(".wav", false) != -1 || token.Find(".ogg", false) != -1) {
#ifdef QUAKE4
if (soundSystemLocal.soundCache && numEntries < maxSamples) {
token.BackSlashesToSlashes();
entries[numEntries] = soundSystem->FindSample(token.c_str());
if (entries[numEntries]) {
numEntries++;
}
}
#else
if (soundSystemLocal.soundCache && numEntries < maxSamples) {
token.BackSlashesToSlashes();
#ifdef PREY
// Prey behavior grabs sys_lang but does not do Doom 3's
// sound/vo/<lang>/ replacement lookup before FindSound.
idStr lang = cvarSystem->GetCVarString("sys_lang");
#else
idStr lang = cvarSystem->GetCVarString("sys_lang");
if (lang.Icmp("english") != 0 && token.Find("sound/vo/", false) >= 0) {
idStr work = token;
work.ToLower();
work.StripLeading("sound/vo/");
work = va("sound/vo/%s/%s", lang.c_str(), work.c_str());
if (fileSystem->ReadFile(work, NULL, NULL) > 0) {
token = work;
}
else {
// also try to find it with the .ogg extension
work.SetFileExtension(".ogg");
if (fileSystem->ReadFile(work, NULL, NULL) > 0) {
token = work;
}
}
}
#endif
entries[numEntries] = soundSystemLocal.soundCache->FindSound(token.c_str(), onDemand);
numEntries++;
}
#endif
}
else {
src.Warning("unknown token '%s'", token.c_str());
return false;
}
}
#ifdef QUAKE4
// if (!soundSystem->GetInsideLevelLoad()) {
// soundSystem->ValidateSoundShader(this);
// }
#else
if (parms.shakes > 0.0f) {
CheckShakesAndOgg();
}
#endif
return true;
}
/*
===============
idSoundShader::CheckShakesAndOgg
===============
*/
bool idSoundShader::CheckShakesAndOgg( void ) const {
int i;
bool ret = false;
for ( i = 0; i < numLeadins; i++ ) {
if ( leadins[ i ]->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) {
common->Warning( "sound shader '%s' has shakes and uses OGG file '%s'",
GetName(), leadins[ i ]->name.c_str() );
ret = true;
}
}
for ( i = 0; i < numEntries; i++ ) {
if ( entries[ i ]->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) {
common->Warning( "sound shader '%s' has shakes and uses OGG file '%s'",
GetName(), entries[ i ]->name.c_str() );
ret = true;
}
}
return ret;
}
/*
===============
idSoundShader::List
===============
*/
void idSoundShader::List() const {
idStrList shaders;
common->Printf( "%4i: %s\n", Index(), GetName() );
if ( idStr::Icmp( GetDescription(), "<no description>" ) != 0 ) {
common->Printf( " description: %s\n", GetDescription() );
}
for( int k = 0; k < numLeadins ; k++ ) {
const idSoundSample *objectp = leadins[k];
if ( objectp ) {
common->Printf( " %5dms %4dKb %s (LEADIN)\n", soundSystemLocal.SamplesToMilliseconds(objectp->LengthIn44kHzSamples()), (objectp->objectMemSize/1024)
,objectp->name.c_str() );
}
}
for( int k = 0; k < numEntries; k++ ) {
const idSoundSample *objectp = entries[k];
if ( objectp ) {
common->Printf( " %5dms %4dKb %s\n", soundSystemLocal.SamplesToMilliseconds(objectp->LengthIn44kHzSamples()), (objectp->objectMemSize/1024)
,objectp->name.c_str() );
}
}
}
/*
===============
idSoundShader::GetAltSound
===============
*/
const idSoundShader *idSoundShader::GetAltSound( void ) const {
return altSound;
}
/*
===============
idSoundShader::GetMinDistance
===============
*/
float idSoundShader::GetMinDistance() const {
return parms.minDistance;
}
/*
===============
idSoundShader::GetMaxDistance
===============
*/
float idSoundShader::GetMaxDistance() const {
return parms.maxDistance;
}
/*
===============
idSoundShader::GetDescription
===============
*/
const char *idSoundShader::GetDescription() const {
return desc;
}
/*
===============
idSoundShader::HasDefaultSound
===============
*/
bool idSoundShader::HasDefaultSound() const {
for ( int i = 0; i < numEntries; i++ ) {
if ( entries[i] && entries[i]->defaultSound ) {
return true;
}
}
return false;
}
/*
===============
idSoundShader::GetParms
===============
*/
const soundShaderParms_t *idSoundShader::GetParms() const {
return &parms;
}
/*
===============
idSoundShader::GetNumSounds
===============
*/
int idSoundShader::GetNumSounds() const {
return numLeadins + numEntries;
}
/*
===============
idSoundShader::GetSound
===============
*/
const char *idSoundShader::GetSound( int index ) const {
if ( index >= 0 ) {
if ( index < numLeadins ) {
return leadins[index]->name.c_str();
}
index -= numLeadins;
if ( index < numEntries ) {
return entries[index]->name.c_str();
}
}
return "";
}
#ifdef QUAKE4
/*
===============
idSoundShader::IsVO_ForPlayer
===============
*/
bool idSoundShader::IsVO_ForPlayer() const
{
return (parms.soundShaderFlags & SSF_VO_FOR_PLAYER) != 0;
}
#endif