2019-12-12 18:21:36 +00:00
/*
* * 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 <stdio.h>
# include <stdlib.h>
# include "s_soundinternal.h"
# include "m_swap.h"
# include "superfasthash.h"
2019-12-22 16:43:39 +00:00
# include "c_cvars.h"
2020-01-27 21:29:45 +00:00
# include "name.h"
# include "filesystem.h"
# include "cmdlib.h"
2020-02-23 13:03:03 +00:00
# include "gamecontrol.h"
2020-02-29 11:33:35 +00:00
# include "serializer.h"
# include "build.h"
2019-12-12 18:21:36 +00:00
enum
{
DEFAULT_PITCH = 128 ,
} ;
SoundEngine * soundEngine ;
int sfx_empty = - 1 ;
//==========================================================================
//
// S_Init
//
//==========================================================================
2019-12-25 22:37:16 +00:00
void SoundEngine : : Init ( TArray < uint8_t > & curve , int factor )
2019-12-12 18:21:36 +00:00
{
2019-12-26 13:43:44 +00:00
StopAllChannels ( ) ;
2019-12-12 18:21:36 +00:00
// Free all channels for use.
while ( Channels ! = NULL )
{
ReturnChannel ( Channels ) ;
}
S_SoundCurve = std : : move ( curve ) ;
2019-12-25 22:37:16 +00:00
SndCurveFactor = ( uint8_t ) factor ;
2019-12-12 18:21:36 +00:00
}
//==========================================================================
//
// SoundEngine::Clear
//
//==========================================================================
void SoundEngine : : Clear ( )
{
StopAllChannels ( ) ;
UnloadAllSounds ( ) ;
GetSounds ( ) . Clear ( ) ;
ClearRandoms ( ) ;
}
//==========================================================================
//
// S_Shutdown
//
//==========================================================================
void SoundEngine : : Shutdown ( )
{
FSoundChan * chan , * next ;
StopAllChannels ( ) ;
for ( chan = FreeChannels ; chan ! = NULL ; chan = next )
{
next = chan - > NextChan ;
delete chan ;
}
FreeChannels = NULL ;
}
//==========================================================================
//
// MarkUsed
//
//==========================================================================
void SoundEngine : : MarkUsed ( int id )
{
if ( ( unsigned ) id < S_sfx . Size ( ) )
{
S_sfx [ id ] . bUsed = true ;
}
}
//==========================================================================
//
// Cache all marked sounds
//
//==========================================================================
void SoundEngine : : CacheMarkedSounds ( )
{
// Don't unload sounds that are playing right now.
for ( FSoundChan * chan = Channels ; chan ! = nullptr ; chan = chan - > NextChan )
{
MarkUsed ( chan - > SoundID ) ;
}
for ( unsigned i = 1 ; i < S_sfx . Size ( ) ; + + i )
{
if ( S_sfx [ i ] . bUsed )
{
CacheSound ( & S_sfx [ i ] ) ;
}
}
for ( unsigned i = 1 ; i < S_sfx . Size ( ) ; + + i )
{
if ( ! S_sfx [ i ] . bUsed & & S_sfx [ i ] . link = = sfxinfo_t : : NO_LINK )
{
UnloadSound ( & S_sfx [ i ] ) ;
}
}
}
//==========================================================================
//
// S_CacheSound
//
//==========================================================================
void SoundEngine : : CacheSound ( sfxinfo_t * sfx )
{
2019-12-15 12:34:00 +00:00
if ( GSnd & & ! sfx - > bTentative )
2019-12-12 18:21:36 +00:00
{
sfxinfo_t * orig = sfx ;
while ( ! sfx - > bRandomHeader & & sfx - > link ! = sfxinfo_t : : NO_LINK )
{
sfx = & S_sfx [ sfx - > link ] ;
}
if ( sfx - > bRandomHeader )
{
CacheRandomSound ( sfx ) ;
}
else
{
// Since we do not know in what format the sound will be used, we have to cache both.
2020-02-09 12:26:51 +00:00
LoadSound ( sfx ) ;
2019-12-12 18:21:36 +00:00
sfx - > bUsed = true ;
}
}
}
//==========================================================================
//
// S_UnloadSound
//
//==========================================================================
void SoundEngine : : UnloadSound ( sfxinfo_t * sfx )
{
if ( sfx - > data . isValid ( ) )
GSnd - > UnloadSound ( sfx - > data ) ;
sfx - > data . Clear ( ) ;
}
//==========================================================================
//
// S_GetChannel
//
// Returns a free channel for the system sound interface.
//
//==========================================================================
FSoundChan * SoundEngine : : GetChannel ( void * syschan )
{
FSoundChan * chan ;
if ( FreeChannels ! = NULL )
{
chan = FreeChannels ;
UnlinkChannel ( chan ) ;
}
else
{
chan = new FSoundChan ;
memset ( chan , 0 , sizeof ( * chan ) ) ;
}
LinkChannel ( chan , & Channels ) ;
chan - > SysChannel = syschan ;
return chan ;
}
//==========================================================================
//
// S_ReturnChannel
//
// Returns a channel to the free pool.
//
//==========================================================================
void SoundEngine : : ReturnChannel ( FSoundChan * chan )
{
UnlinkChannel ( chan ) ;
memset ( chan , 0 , sizeof ( * chan ) ) ;
LinkChannel ( chan , & FreeChannels ) ;
}
//==========================================================================
//
// S_UnlinkChannel
//
//==========================================================================
void SoundEngine : : UnlinkChannel ( FSoundChan * chan )
{
* ( chan - > PrevChan ) = chan - > NextChan ;
if ( chan - > NextChan ! = NULL )
{
chan - > NextChan - > PrevChan = chan - > PrevChan ;
}
}
//==========================================================================
//
// S_LinkChannel
//
//==========================================================================
void SoundEngine : : LinkChannel ( FSoundChan * chan , FSoundChan * * head )
{
chan - > NextChan = * head ;
if ( chan - > NextChan ! = NULL )
{
chan - > NextChan - > PrevChan = & chan - > NextChan ;
}
* head = chan ;
chan - > PrevChan = head ;
}
//==========================================================================
//
//
//
//==========================================================================
TArray < FSoundChan * > SoundEngine : : AllActiveChannels ( )
{
TArray < FSoundChan * > chans ;
for ( auto chan = Channels ; chan ! = nullptr ; chan = chan - > NextChan )
{
// If the sound is forgettable, this is as good a time as
// any to forget about it. And if it's a UI sound, it shouldn't
// be stored in the savegame.
2020-02-29 11:33:35 +00:00
if ( ! ( chan - > ChanFlags & ( CHANF_FORGETTABLE | CHANF_UI | CHANF_TRANSIENT ) ) )
2019-12-12 18:21:36 +00:00
{
chans . Push ( chan ) ;
}
}
return chans ;
}
//==========================================================================
//
//
//
//==========================================================================
FString SoundEngine : : ListSoundChannels ( )
{
FString output ;
FSoundChan * chan ;
int count = 0 ;
for ( chan = Channels ; chan ! = nullptr ; chan = chan - > NextChan )
{
2019-12-16 23:29:38 +00:00
if ( ! ( chan - > ChanFlags & CHANF_EVICTED ) )
2019-12-12 18:21:36 +00:00
{
FVector3 chanorigin ;
CalcPosVel ( chan , & chanorigin , nullptr ) ;
output . AppendFormat ( " %s at (%1.5f, %1.5f, %1.5f) \n " , ( const char * ) S_sfx [ chan - > SoundID ] . name . GetChars ( ) , chanorigin . X , chanorigin . Y , chanorigin . Z ) ;
count + + ;
}
}
output . AppendFormat ( " %d sounds playing \n " , count ) ;
return output ;
}
// [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.
//
//=========================================================================
void SoundEngine : : CalcPosVel ( FSoundChan * chan , FVector3 * pos , FVector3 * vel )
{
2019-12-18 10:09:01 +00:00
CalcPosVel ( chan - > SourceType , chan - > Source , chan - > Point , chan - > EntChannel , chan - > ChanFlags , chan - > OrgID , pos , vel , chan ) ;
2019-12-12 18:21:36 +00:00
}
bool SoundEngine : : ValidatePosVel ( const FSoundChan * const chan , const FVector3 & pos , const FVector3 & vel )
{
return ValidatePosVel ( chan - > SourceType , chan - > Source , pos , vel ) ;
}
//==========================================================================
//
//
//
//==========================================================================
FSoundID SoundEngine : : ResolveSound ( const void * , int , FSoundID soundid , float & attenuation )
{
const sfxinfo_t & sfx = S_sfx [ soundid ] ;
if ( sfx . bRandomHeader )
{
// Random sounds attenuate based on the original (random) sound as well as the chosen one.
attenuation * = sfx . Attenuation ;
return PickReplacement ( soundid ) ;
}
else
{
return sfx . link ;
}
}
//==========================================================================
//
// S_StartSound
//
// 0 attenuation means full volume over whole primaryLevel->
// 0 < attenuation means to scale the distance by that amount when
// calculating volume.
//
//==========================================================================
FSoundChan * SoundEngine : : StartSound ( int type , const void * source ,
2019-12-16 23:29:38 +00:00
const FVector3 * pt , int channel , EChanFlags flags , FSoundID sound_id , float volume , float attenuation ,
2019-12-12 18:21:36 +00:00
FRolloffInfo * forcedrolloff , float spitch )
{
sfxinfo_t * sfx ;
2019-12-16 23:29:38 +00:00
EChanFlags chanflags = flags ;
2019-12-12 18:21:36 +00:00
int basepriority ;
int org_id ;
int pitch ;
FSoundChan * chan ;
FVector3 pos , vel ;
FRolloffInfo * rolloff ;
2020-02-23 13:03:03 +00:00
if ( sound_id < = 0 | | volume < = 0 | | userConfig . nosound )
2019-12-12 18:21:36 +00:00
return NULL ;
// prevent crashes.
if ( type = = SOURCE_Unattached & & pt = = nullptr ) type = SOURCE_None ;
org_id = sound_id ;
2019-12-18 10:09:01 +00:00
CalcPosVel ( type , source , & pt - > X , channel , chanflags , sound_id , & pos , & vel , nullptr ) ;
2019-12-12 18:21:36 +00:00
if ( ! ValidatePosVel ( type , source , pos , vel ) )
{
return nullptr ;
}
sfx = & S_sfx [ sound_id ] ;
// Scale volume according to SNDINFO data.
volume = std : : 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 ;
auto pitchmask = sfx - > PitchMask ;
rolloff = & sfx - > Rolloff ;
// Resolve player sounds, random sounds, and aliases
while ( sfx - > link ! = sfxinfo_t : : NO_LINK )
{
sound_id = ResolveSound ( source , type , sound_id , attenuation ) ;
if ( sound_id < 0 ) return nullptr ;
auto newsfx = & S_sfx [ sound_id ] ;
if ( newsfx ! = sfx )
{
if ( near_limit < 0 )
{
near_limit = newsfx - > NearLimit ;
limit_range = newsfx - > LimitRange ;
}
if ( rolloff - > MinDistance = = 0 )
{
rolloff = & newsfx - > Rolloff ;
}
sfx = newsfx ;
}
else return nullptr ; // nothing got replaced, prevent an endless loop,
}
// 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 & & CheckSingular ( sound_id ) )
{
2019-12-16 23:29:38 +00:00
chanflags | = CHANF_EVICTED ;
2019-12-12 18:21:36 +00:00
}
// If the sound is unpositioned or comes from the listener, it is
// never limited.
if ( type = = SOURCE_None | | source = = listener . ListenerObject )
{
near_limit = 0 ;
}
// If this sound doesn't like playing near itself, don't play it if
// that's what would happen. (Does this really need the SOURCE_Actor restriction?)
if ( near_limit > 0 & & CheckSoundLimit ( sfx , pos , near_limit , limit_range , type , type = = SOURCE_Actor ? source : nullptr , channel ) )
{
2019-12-16 23:29:38 +00:00
chanflags | = CHANF_EVICTED ;
2019-12-12 18:21:36 +00:00
}
// 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.
2019-12-19 00:20:43 +00:00
if ( ( chanflags & ( CHANF_EVICTED | CHANF_LOOP ) ) = = CHANF_EVICTED )
2019-12-12 18:21:36 +00:00
{
return NULL ;
}
// Make sure the sound is loaded.
2020-02-09 12:26:51 +00:00
sfx = LoadSound ( sfx ) ;
2019-12-12 18:21:36 +00:00
// The empty sound never plays.
if ( sfx - > lumpnum = = sfx_empty )
{
return NULL ;
}
// Select priority.
if ( type = = SOURCE_None | | source = = listener . ListenerObject )
{
basepriority = 80 ;
}
else
{
basepriority = 0 ;
}
int seen = 0 ;
if ( source ! = NULL & & channel = = CHAN_AUTO )
{
// Select a channel that isn't already playing something.
// Try channel 0 first, then travel from channel 7 down.
if ( ! IsChannelUsed ( type , source , 0 , & seen ) )
{
channel = 0 ;
}
else
{
for ( channel = 7 ; channel > 0 ; - - channel )
{
if ( ! IsChannelUsed ( type , source , channel , & seen ) )
{
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-16 23:29:38 +00:00
if ( ! ( chanflags & CHANF_OVERLAP ) & & type ! = SOURCE_None & & ( ( source = = NULL & & channel ! = CHAN_AUTO ) | | ( source ! = NULL & & IsChannelUsed ( type , source , channel , & seen ) ) ) )
2019-12-12 18:21:36 +00:00
{
for ( chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
if ( chan - > SourceType = = type & & chan - > EntChannel = = channel )
{
const bool foundit = ( type = = SOURCE_Unattached )
? ( chan - > Point [ 0 ] = = pt - > X & & chan - > Point [ 2 ] = = pt - > Z & & chan - > Point [ 1 ] = = pt - > Y )
: ( chan - > Source = = source ) ;
if ( foundit )
{
StopChannel ( chan ) ;
}
}
}
}
// sound is paused and a non-looped sound is being started.
// Such a sound would play right after unpausing which wouldn't sound right.
2019-12-19 00:20:43 +00:00
if ( ! ( chanflags & CHANF_LOOP ) & & ! ( chanflags & ( CHANF_UI | CHANF_NOPAUSE ) ) & & SoundPaused )
2019-12-12 18:21:36 +00:00
{
return NULL ;
}
// Vary the sfx pitches.
if ( pitchmask ! = 0 )
{
pitch = DEFAULT_PITCH - ( rand ( ) & pitchmask ) + ( rand ( ) & pitchmask ) ;
}
else
{
pitch = DEFAULT_PITCH ;
}
2019-12-16 23:29:38 +00:00
if ( chanflags & CHANF_EVICTED )
2019-12-12 18:21:36 +00:00
{
chan = NULL ;
}
else
{
int startflags = 0 ;
2019-12-16 23:29:38 +00:00
if ( chanflags & CHANF_LOOP ) startflags | = SNDF_LOOP ;
if ( chanflags & CHANF_AREA ) startflags | = SNDF_AREA ;
if ( chanflags & ( CHANF_UI | CHANF_NOPAUSE ) ) startflags | = SNDF_NOPAUSE ;
if ( chanflags & CHANF_UI ) startflags | = SNDF_NOREVERB ;
2019-12-12 18:21:36 +00:00
2019-12-19 08:43:43 +00:00
if ( attenuation > 0 & & type ! = SOURCE_None )
2019-12-12 18:21:36 +00:00
{
2020-02-09 12:26:51 +00:00
chan = ( FSoundChan * ) GSnd - > StartSound3D ( sfx - > data , & listener , float ( volume ) , rolloff , float ( attenuation ) , pitch , basepriority , pos , vel , channel , startflags , NULL ) ;
2019-12-12 18:21:36 +00:00
}
else
{
chan = ( FSoundChan * ) GSnd - > StartSound ( sfx - > data , float ( volume ) , pitch , startflags , NULL ) ;
}
}
2019-12-19 00:20:43 +00:00
if ( chan = = NULL & & ( chanflags & CHANF_LOOP ) )
2019-12-12 18:21:36 +00:00
{
chan = ( FSoundChan * ) GetChannel ( NULL ) ;
GSnd - > MarkStartTime ( chan ) ;
2019-12-16 23:29:38 +00:00
chanflags | = CHANF_EVICTED ;
2019-12-12 18:21:36 +00:00
}
2020-02-22 16:23:23 +00:00
if ( attenuation > 0 & & type ! = SOURCE_None )
2019-12-12 18:21:36 +00:00
{
2019-12-16 23:29:38 +00:00
chanflags | = CHANF_IS3D | CHANF_JUSTSTARTED ;
2019-12-12 18:21:36 +00:00
}
else
{
2019-12-16 23:29:38 +00:00
chanflags | = CHANF_LISTENERZ | CHANF_JUSTSTARTED ;
2019-12-12 18:21:36 +00:00
}
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 ;
if ( type = = SOURCE_Unattached )
{
chan - > Point [ 0 ] = pt - > X ; chan - > Point [ 1 ] = pt - > Y ; chan - > Point [ 2 ] = pt - > Z ;
}
else if ( type ! = SOURCE_None )
{
chan - > Source = source ;
}
if ( spitch > 0.0 )
SetPitch ( chan , spitch ) ;
}
return chan ;
}
//==========================================================================
//
// S_RestartSound
//
// Attempts to restart looping sounds that were evicted from their channels.
//
//==========================================================================
void SoundEngine : : RestartChannel ( FSoundChan * chan )
{
2019-12-19 00:20:43 +00:00
assert ( chan - > ChanFlags & CHANF_EVICTED ) ;
2019-12-12 18:21:36 +00:00
FSoundChan * ochan ;
sfxinfo_t * sfx = & S_sfx [ chan - > SoundID ] ;
// If this is a singular sound, don't play it if it's already playing.
if ( sfx - > bSingular & & CheckSingular ( chan - > SoundID ) )
return ;
2020-02-09 12:26:51 +00:00
sfx = LoadSound ( sfx ) ;
2019-12-12 18:21:36 +00:00
// The empty sound never plays.
if ( sfx - > lumpnum = = sfx_empty )
{
return ;
}
2019-12-16 23:29:38 +00:00
EChanFlags oldflags = chan - > ChanFlags ;
2019-12-12 18:21:36 +00:00
int startflags = 0 ;
2019-12-16 23:29:38 +00:00
if ( chan - > ChanFlags & CHANF_LOOP ) startflags | = SNDF_LOOP ;
if ( chan - > ChanFlags & CHANF_AREA ) startflags | = SNDF_AREA ;
if ( chan - > ChanFlags & ( CHANF_UI | CHANF_NOPAUSE ) ) startflags | = SNDF_NOPAUSE ;
if ( chan - > ChanFlags & CHANF_ABSTIME ) startflags | = SNDF_ABSTIME ;
2019-12-12 18:21:36 +00:00
2019-12-16 23:29:38 +00:00
if ( chan - > ChanFlags & CHANF_IS3D )
2019-12-12 18:21:36 +00:00
{
FVector3 pos , vel ;
CalcPosVel ( chan , & pos , & vel ) ;
if ( ! ValidatePosVel ( chan , pos , vel ) )
{
return ;
}
// If this sound doesn't like playing near itself, don't play it if
// that's what would happen.
if ( chan - > NearLimit > 0 & & CheckSoundLimit ( & S_sfx [ chan - > SoundID ] , pos , chan - > NearLimit , chan - > LimitRange , 0 , NULL , 0 ) )
{
return ;
}
2019-12-16 23:29:38 +00:00
chan - > ChanFlags & = ~ ( CHANF_EVICTED | CHANF_ABSTIME ) ;
2020-02-09 12:26:51 +00:00
ochan = ( FSoundChan * ) GSnd - > StartSound3D ( sfx - > data , & listener , chan - > Volume , & chan - > Rolloff , chan - > DistanceScale , chan - > Pitch ,
2019-12-12 18:21:36 +00:00
chan - > Priority , pos , vel , chan - > EntChannel , startflags , chan ) ;
}
else
{
2019-12-16 23:29:38 +00:00
chan - > ChanFlags & = ~ ( CHANF_EVICTED | CHANF_ABSTIME ) ;
2019-12-12 18:21:36 +00:00
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.
//
//==========================================================================
2020-02-09 12:26:51 +00:00
sfxinfo_t * SoundEngine : : LoadSound ( sfxinfo_t * sfx )
2019-12-12 18:21:36 +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 + + )
{
2019-12-17 18:37:05 +00:00
if ( S_sfx [ i ] . data . isValid ( ) & & S_sfx [ i ] . link = = sfxinfo_t : : NO_LINK & & S_sfx [ i ] . lumpnum = = sfx - > lumpnum & &
( ! sfx - > bLoadRAW | | ( sfx - > RawRate = = S_sfx [ i ] . RawRate ) ) ) // Raw sounds with different sample rates may not share buffers, even if they use the same source data.
2019-12-12 18:21:36 +00:00
{
//DPrintf (DMSG_NOTIFY, "Linked %s to %s (%d)\n", sfx->name.GetChars(), S_sfx[i].name.GetChars(), i);
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 ] ;
}
}
//DPrintf(DMSG_NOTIFY, "Loading sound \"%s\" (%td)\n", sfx->name.GetChars(), sfx - &S_sfx[0]);
auto sfxdata = ReadSound ( sfx - > lumpnum ) ;
int size = sfxdata . Size ( ) ;
if ( size > 8 )
{
int32_t dmxlen = LittleLong ( ( ( int32_t * ) sfxdata . Data ( ) ) [ 1 ] ) ;
// If the sound is voc, use the custom loader.
if ( strncmp ( ( const char * ) sfxdata . Data ( ) , " Creative Voice File " , 19 ) = = 0 )
{
2020-02-09 12:26:51 +00:00
sfx - > data = GSnd - > LoadSoundVoc ( sfxdata . Data ( ) , size ) ;
2019-12-12 18:21:36 +00:00
}
// If the sound is raw, just load it as such.
else if ( sfx - > bLoadRAW )
{
2020-02-09 12:26:51 +00:00
sfx - > data = GSnd - > LoadSoundRaw ( sfxdata . Data ( ) , size , sfx - > RawRate , 1 , 8 , sfx - > LoopStart ) ;
2019-12-12 18:21:36 +00:00
}
// Otherwise, try the sound as DMX format.
else if ( ( ( uint8_t * ) sfxdata . Data ( ) ) [ 0 ] = = 3 & & ( ( uint8_t * ) sfxdata . Data ( ) ) [ 1 ] = = 0 & & dmxlen < = size - 8 )
{
int frequency = LittleShort ( ( ( uint16_t * ) sfxdata . Data ( ) ) [ 1 ] ) ;
if ( frequency = = 0 ) frequency = 11025 ;
2020-02-09 12:26:51 +00:00
sfx - > data = GSnd - > LoadSoundRaw ( sfxdata . Data ( ) + 8 , dmxlen , frequency , 1 , 8 , sfx - > LoopStart ) ;
2019-12-12 18:21:36 +00:00
}
// If that fails, let the sound system try and figure it out.
else
{
2020-02-09 12:26:51 +00:00
sfx - > data = GSnd - > LoadSound ( sfxdata . Data ( ) , size ) ;
2019-12-12 18:21:36 +00:00
}
}
if ( ! sfx - > data . isValid ( ) )
{
if ( sfx - > lumpnum ! = sfx_empty )
{
sfx - > lumpnum = sfx_empty ;
continue ;
}
}
break ;
}
return sfx ;
}
//==========================================================================
//
// S_CheckSingular
//
// Returns true if a copy of this sound is already playing.
//
//==========================================================================
bool SoundEngine : : 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 SoundEngine : : CheckSoundLimit ( sfxinfo_t * sfx , const FVector3 & pos , int near_limit , float limit_range ,
int sourcetype , const void * actor , int channel )
{
FSoundChan * chan ;
int count ;
for ( chan = Channels , count = 0 ; chan ! = NULL & & count < near_limit ; chan = chan - > NextChan )
{
2019-12-27 16:08:48 +00:00
if ( chan - > ChanFlags & CHANF_FORGETTABLE ) continue ;
2019-12-16 23:29:38 +00:00
if ( ! ( chan - > ChanFlags & CHANF_EVICTED ) & & & S_sfx [ chan - > SoundID ] = = sfx )
2019-12-12 18:21:36 +00:00
{
FVector3 chanorigin ;
if ( actor ! = NULL & & chan - > EntChannel = = channel & &
chan - > SourceType = = sourcetype & & chan - > Source = = actor )
{ // 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.
//
//==========================================================================
2019-12-12 20:42:58 +00:00
void SoundEngine : : StopSoundID ( int sound_id )
{
FSoundChan * chan = Channels ;
while ( chan ! = NULL )
{
FSoundChan * next = chan - > NextChan ;
2019-12-16 16:41:44 +00:00
if ( sound_id = = chan - > OrgID )
2019-12-12 20:42:58 +00:00
{
StopChannel ( chan ) ;
}
chan = next ;
}
}
//==========================================================================
//
// S_StopSound
//
// Stops an unpositioned sound from playing on a specific channel.
//
//==========================================================================
void SoundEngine : : StopSound ( int channel , int sound_id )
2019-12-12 18:21:36 +00:00
{
FSoundChan * chan = Channels ;
while ( chan ! = NULL )
{
FSoundChan * next = chan - > NextChan ;
2019-12-16 23:29:38 +00:00
if ( ( chan - > SourceType = = SOURCE_None & & ( sound_id = = - 1 | | sound_id = = chan - > OrgID ) ) & & ( channel = = CHAN_AUTO | | channel = = chan - > EntChannel ) )
2019-12-12 18:21:36 +00:00
{
StopChannel ( chan ) ;
}
chan = next ;
}
}
//==========================================================================
//
// S_StopSound
//
// Stops a sound from a single actor from playing on a specific channel.
//
//==========================================================================
2019-12-12 20:42:58 +00:00
void SoundEngine : : StopSound ( int sourcetype , const void * actor , int channel , int sound_id )
2019-12-12 18:21:36 +00:00
{
FSoundChan * chan = Channels ;
while ( chan ! = NULL )
{
FSoundChan * next = chan - > NextChan ;
if ( chan - > SourceType = = sourcetype & &
chan - > Source = = actor & &
2019-12-16 16:41:44 +00:00
( sound_id = = - 1 ? ( chan - > EntChannel = = channel | | channel < 0 ) : ( chan - > OrgID = = sound_id ) ) )
2019-12-12 18:21:36 +00:00
{
StopChannel ( chan ) ;
}
chan = next ;
}
}
//==========================================================================
//
// S_StopAllChannels
//
//==========================================================================
void SoundEngine : : StopAllChannels ( )
{
FSoundChan * chan = Channels ;
while ( chan ! = NULL )
{
FSoundChan * next = chan - > NextChan ;
StopChannel ( chan ) ;
chan = next ;
}
if ( GSnd )
GSnd - > UpdateSounds ( ) ;
}
//==========================================================================
//
// S_RelinkSound
//
// Moves all the sounds from one thing to another. If the destination is
// NULL, then the sound becomes a positioned sound.
//==========================================================================
void SoundEngine : : RelinkSound ( int sourcetype , const void * from , const void * to , const FVector3 * optpos )
{
if ( from = = NULL )
return ;
FSoundChan * chan = Channels ;
while ( chan ! = NULL )
{
FSoundChan * next = chan - > NextChan ;
if ( chan - > SourceType = = sourcetype & & chan - > Source = = from )
{
if ( to ! = NULL )
{
chan - > Source = to ;
}
2019-12-19 00:20:43 +00:00
else if ( ! ( chan - > ChanFlags & CHANF_LOOP ) & & optpos )
2019-12-12 18:21:36 +00:00
{
chan - > Source = NULL ;
chan - > SourceType = SOURCE_Unattached ;
chan - > Point [ 0 ] = optpos - > X ;
chan - > Point [ 1 ] = optpos - > Y ;
chan - > Point [ 2 ] = optpos - > Z ;
}
else
{
StopChannel ( chan ) ;
}
}
chan = next ;
}
}
//==========================================================================
//
// S_ChangeSoundVolume
//
//==========================================================================
void SoundEngine : : ChangeSoundVolume ( int sourcetype , const void * source , int channel , double dvolume )
{
float volume = float ( dvolume ) ;
// don't let volume get out of bounds
if ( volume < 0.0 )
volume = 0.0 ;
else if ( volume > 1.0 )
volume = 1.0 ;
for ( FSoundChan * chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
if ( chan - > SourceType = = sourcetype & &
chan - > Source = = source & &
( chan - > EntChannel = = channel | | channel = = - 1 ) )
{
GSnd - > ChannelVolume ( chan , volume ) ;
chan - > Volume = volume ;
}
}
return ;
}
2019-12-18 21:13:19 +00:00
void SoundEngine : : SetVolume ( FSoundChan * chan , float volume )
{
if ( volume < 0.0 ) volume = 0.0 ;
else if ( volume > 1.0 ) volume = 1.0 ;
assert ( chan ! = nullptr ) ;
GSnd - > ChannelVolume ( chan , volume ) ;
chan - > Volume = volume ;
}
2019-12-12 18:21:36 +00:00
//==========================================================================
//
// S_ChangeSoundPitch
//
//==========================================================================
2019-12-12 20:42:58 +00:00
void SoundEngine : : ChangeSoundPitch ( int sourcetype , const void * source , int channel , double pitch , int sound_id )
2019-12-12 18:21:36 +00:00
{
for ( FSoundChan * chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
if ( chan - > SourceType = = sourcetype & &
chan - > Source = = source & &
2019-12-16 16:41:44 +00:00
( sound_id = = - 1 ? ( chan - > EntChannel = = channel ) : ( chan - > OrgID = = sound_id ) ) )
2019-12-12 18:21:36 +00:00
{
SetPitch ( chan , ( float ) pitch ) ;
}
}
return ;
}
void SoundEngine : : SetPitch ( FSoundChan * chan , float pitch )
{
assert ( chan ! = nullptr ) ;
GSnd - > ChannelPitch ( chan , std : : max ( 0.0001f , pitch ) ) ;
chan - > Pitch = std : : max ( 1 , int ( float ( DEFAULT_PITCH ) * pitch ) ) ;
}
//==========================================================================
//
// S_GetSoundPlayingInfo
//
// Is a sound being played by a specific emitter?
//==========================================================================
2019-12-15 12:34:00 +00:00
int SoundEngine : : GetSoundPlayingInfo ( int sourcetype , const void * source , int sound_id )
2019-12-12 18:21:36 +00:00
{
2019-12-15 12:34:00 +00:00
int count = 0 ;
2019-12-12 18:21:36 +00:00
if ( sound_id > 0 )
{
for ( FSoundChan * chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
2019-12-12 20:42:58 +00:00
if ( chan - > OrgID = = sound_id & & ( sourcetype = = SOURCE_Any | |
( chan - > SourceType = = sourcetype & &
chan - > Source = = source ) ) )
2019-12-12 18:21:36 +00:00
{
2019-12-15 12:34:00 +00:00
count + + ;
2019-12-12 18:21:36 +00:00
}
}
}
2019-12-15 12:34:00 +00:00
return count ;
2019-12-12 18:21:36 +00:00
}
//==========================================================================
//
// 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.
//
//==========================================================================
bool SoundEngine : : IsChannelUsed ( int sourcetype , const void * actor , int channel , int * seen )
{
if ( * seen & ( 1 < < channel ) )
{
return true ;
}
for ( FSoundChan * chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
if ( chan - > SourceType = = sourcetype & & chan - > Source = = actor )
{
* seen | = 1 < < chan - > EntChannel ;
if ( chan - > EntChannel = = channel )
{
return true ;
}
}
}
return false ;
}
//==========================================================================
//
// S_IsActorPlayingSomething
//
//==========================================================================
bool SoundEngine : : IsSourcePlayingSomething ( int sourcetype , const void * actor , int channel , int sound_id )
{
for ( FSoundChan * chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
2019-12-18 18:17:37 +00:00
if ( chan - > SourceType = = sourcetype & & ( sourcetype = = SOURCE_None | | sourcetype = = SOURCE_Unattached | | chan - > Source = = actor ) )
2019-12-12 18:21:36 +00:00
{
2019-12-19 10:47:47 +00:00
if ( ( channel = = 0 | | chan - > EntChannel = = channel ) & & ( sound_id < = 0 | | chan - > OrgID = = sound_id ) )
2019-12-12 18:21:36 +00:00
{
2019-12-19 10:47:47 +00:00
return true ;
2019-12-12 18:21:36 +00:00
}
}
}
return false ;
}
//==========================================================================
//
// S_EvictAllChannels
//
// Forcibly evicts all channels so that there are none playing, but all
// information needed to restart them is retained.
//
//==========================================================================
void SoundEngine : : EvictAllChannels ( )
{
FSoundChan * chan , * next ;
for ( chan = Channels ; chan ! = NULL ; chan = next )
{
next = chan - > NextChan ;
2019-12-19 00:20:43 +00:00
if ( ! ( chan - > ChanFlags & CHANF_EVICTED ) )
{
chan - > ChanFlags | = CHANF_EVICTED ;
if ( chan - > SysChannel ! = NULL )
{
if ( ! ( chan - > ChanFlags & CHANF_ABSTIME ) )
{
chan - > StartTime = GSnd ? GSnd - > GetPosition ( chan ) : 0 ;
chan - > ChanFlags | = CHANF_ABSTIME ;
}
StopChannel ( chan ) ;
}
// assert(chan->NextChan == next);
}
2019-12-12 18:21:36 +00:00
}
}
//==========================================================================
//
// S_RestoreEvictedChannel
//
// Recursive helper for S_RestoreEvictedChannels().
//
//==========================================================================
void SoundEngine : : RestoreEvictedChannel ( FSoundChan * chan )
{
if ( chan = = NULL )
{
return ;
}
RestoreEvictedChannel ( chan - > NextChan ) ;
2019-12-16 23:29:38 +00:00
if ( chan - > ChanFlags & CHANF_EVICTED )
2019-12-12 18:21:36 +00:00
{
RestartChannel ( chan ) ;
2019-12-19 00:20:43 +00:00
if ( ! ( chan - > ChanFlags & CHANF_LOOP ) )
2019-12-12 18:21:36 +00:00
{
2019-12-16 23:29:38 +00:00
if ( chan - > ChanFlags & CHANF_EVICTED )
2019-12-12 18:21:36 +00:00
{ // Still evicted and not looping? Forget about it.
ReturnChannel ( chan ) ;
}
2019-12-16 23:29:38 +00:00
else if ( ! ( chan - > ChanFlags & CHANF_JUSTSTARTED ) )
2019-12-12 18:21:36 +00:00
{ // Should this sound become evicted again, it's okay to forget about it.
2019-12-16 23:29:38 +00:00
chan - > ChanFlags | = CHANF_FORGETTABLE ;
2019-12-12 18:21:36 +00:00
}
}
}
2019-12-19 00:20:43 +00:00
else if ( chan - > SysChannel = = NULL & & ( chan - > ChanFlags & ( CHANF_FORGETTABLE | CHANF_LOOP ) ) = = CHANF_FORGETTABLE )
2019-12-12 18:21:36 +00:00
{
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 SoundEngine : : RestoreEvictedChannels ( )
{
// Restart channels in the same order they were originally played.
RestoreEvictedChannel ( Channels ) ;
}
//==========================================================================
//
// S_UpdateSounds
//
// Updates music & sounds
//==========================================================================
void SoundEngine : : UpdateSounds ( int time )
{
FVector3 pos , vel ;
for ( FSoundChan * chan = Channels ; chan ! = NULL ; chan = chan - > NextChan )
{
2019-12-16 23:29:38 +00:00
if ( ( chan - > ChanFlags & ( CHANF_EVICTED | CHANF_IS3D ) ) = = CHANF_IS3D )
2019-12-12 18:21:36 +00:00
{
CalcPosVel ( chan , & pos , & vel ) ;
if ( ValidatePosVel ( chan , pos , vel ) )
{
2019-12-16 23:29:38 +00:00
GSnd - > UpdateSoundParams3D ( & listener , chan , ! ! ( chan - > ChanFlags & CHANF_AREA ) , pos , vel ) ;
2019-12-12 18:21:36 +00:00
}
}
2019-12-16 23:29:38 +00:00
chan - > ChanFlags & = ~ CHANF_JUSTSTARTED ;
2019-12-12 18:21:36 +00:00
}
GSnd - > UpdateListener ( & listener ) ;
GSnd - > UpdateSounds ( ) ;
if ( time > = RestartEvictionsAt )
{
RestartEvictionsAt = 0 ;
RestoreEvictedChannels ( ) ;
}
}
//==========================================================================
//
// S_GetRolloff
//
//==========================================================================
float SoundEngine : : GetRolloff ( const FRolloffInfo * rolloff , float distance )
{
if ( rolloff = = NULL )
{
return 0 ;
}
if ( distance < = rolloff - > MinDistance )
{
return 1.f ;
}
// Logarithmic rolloff has no max distance where it goes silent.
if ( rolloff - > RolloffType = = ROLLOFF_Log )
{
return rolloff - > MinDistance / ( rolloff - > MinDistance + rolloff - > RolloffFactor * ( distance - rolloff - > MinDistance ) ) ;
}
if ( distance > = rolloff - > MaxDistance )
{
return 0.f ;
}
float volume = ( rolloff - > MaxDistance - distance ) / ( rolloff - > MaxDistance - rolloff - > MinDistance ) ;
if ( rolloff - > RolloffType = = ROLLOFF_Linear )
{
return volume ;
}
if ( rolloff - > RolloffType = = ROLLOFF_Custom & & S_SoundCurve . Size ( ) > 0 )
{
2019-12-25 22:37:16 +00:00
return S_SoundCurve [ int ( S_SoundCurve . Size ( ) * ( 1.f - volume ) ) ] / ( float ) SndCurveFactor ;
2019-12-12 18:21:36 +00:00
}
return ( powf ( 10.f , volume ) - 1.f ) / 9.f ;
}
//==========================================================================
//
// S_ChannelEnded (callback for sound interface code)
//
//==========================================================================
void SoundEngine : : ChannelEnded ( FISoundChannel * ichan )
{
FSoundChan * schan = static_cast < FSoundChan * > ( ichan ) ;
bool evicted ;
2019-12-27 19:05:58 +00:00
schan - > ChanFlags & = ~ CHANF_ENDED ;
2019-12-12 18:21:36 +00:00
if ( schan ! = NULL )
{
// If the sound was stopped with GSnd->StopSound(), then we know
2019-12-19 00:20:43 +00:00
// it wasn't evicted. Otherwise, if it's looping, it must have
2019-12-12 18:21:36 +00:00
// been evicted. If it's not looping, then it was evicted if it
// didn't reach the end of its playback.
2019-12-16 23:29:38 +00:00
if ( schan - > ChanFlags & CHANF_FORGETTABLE )
2019-12-12 18:21:36 +00:00
{
evicted = false ;
}
2019-12-19 00:20:43 +00:00
else if ( schan - > ChanFlags & ( CHANF_LOOP | CHANF_EVICTED ) )
2019-12-12 18:21:36 +00:00
{
evicted = true ;
}
else
{
unsigned int pos = GSnd - > GetPosition ( schan ) ;
unsigned int len = GSnd - > GetSampleLength ( S_sfx [ schan - > SoundID ] . data ) ;
if ( pos = = 0 )
{
2019-12-16 23:29:38 +00:00
evicted = ! ! ( schan - > ChanFlags & CHANF_JUSTSTARTED ) ;
2019-12-12 18:21:36 +00:00
}
else
{
evicted = ( pos < len ) ;
}
}
if ( ! evicted )
{
ReturnChannel ( schan ) ;
}
else
{
2019-12-16 23:29:38 +00:00
schan - > ChanFlags | = CHANF_EVICTED ;
2019-12-12 18:21:36 +00:00
schan - > SysChannel = NULL ;
}
}
}
//==========================================================================
//
// S_ChannelVirtualChanged (callback for sound interface code)
//
//==========================================================================
void SoundEngine : : ChannelVirtualChanged ( FISoundChannel * ichan , bool is_virtual )
{
FSoundChan * schan = static_cast < FSoundChan * > ( ichan ) ;
if ( is_virtual )
{
2019-12-16 23:29:38 +00:00
schan - > ChanFlags | = CHANF_VIRTUAL ;
2019-12-12 18:21:36 +00:00
}
else
{
2019-12-16 23:29:38 +00:00
schan - > ChanFlags & = ~ CHANF_VIRTUAL ;
2019-12-12 18:21:36 +00:00
}
}
//==========================================================================
//
// StopChannel
//
//==========================================================================
void SoundEngine : : 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.
2019-12-16 23:29:38 +00:00
if ( ! ( chan - > ChanFlags & CHANF_EVICTED ) )
2019-12-12 18:21:36 +00:00
{
2019-12-16 23:29:38 +00:00
chan - > ChanFlags | = CHANF_FORGETTABLE ;
2019-12-12 18:21:36 +00:00
if ( chan - > SourceType = = SOURCE_Actor )
{
chan - > Source = NULL ;
2019-12-27 16:08:48 +00:00
chan - > SourceType = SOURCE_Unattached ;
2019-12-12 18:21:36 +00:00
}
}
2019-12-26 13:43:44 +00:00
if ( GSnd ) GSnd - > StopChannel ( chan ) ;
2019-12-12 18:21:36 +00:00
}
else
{
ReturnChannel ( chan ) ;
}
}
void SoundEngine : : UnloadAllSounds ( )
{
for ( unsigned i = 0 ; i < S_sfx . Size ( ) ; i + + )
{
UnloadSound ( & S_sfx [ i ] ) ;
}
}
void SoundEngine : : Reset ( )
{
EvictAllChannels ( ) ;
I_CloseSound ( ) ;
I_InitSound ( ) ;
RestoreEvictedChannels ( ) ;
}
//==========================================================================
//
// S_FindSound
//
// Given a logical name, find the sound's index in S_sfx.
//==========================================================================
int SoundEngine : : FindSound ( const char * logicalname )
{
int i ;
if ( logicalname ! = NULL )
{
i = S_sfx [ MakeKey ( logicalname ) % S_sfx . Size ( ) ] . index ;
while ( ( i ! = 0 ) & & stricmp ( S_sfx [ i ] . name , logicalname ) )
i = S_sfx [ i ] . next ;
return i ;
}
else
{
return 0 ;
}
}
int SoundEngine : : FindSoundByResID ( int resid )
{
auto p = ResIdMap . CheckKey ( resid ) ;
return p ? * p : 0 ;
}
//==========================================================================
//
// S_FindSoundNoHash
//
// Given a logical name, find the sound's index in S_sfx without
// using the hash table.
//==========================================================================
int SoundEngine : : FindSoundNoHash ( const char * logicalname )
{
unsigned int i ;
for ( i = 1 ; i < S_sfx . Size ( ) ; i + + )
{
if ( stricmp ( S_sfx [ i ] . name , logicalname ) = = 0 )
{
return i ;
}
}
return 0 ;
}
//==========================================================================
//
// S_FindSoundByLump
//
// Given a sound lump, find the sound's index in S_sfx.
//==========================================================================
int SoundEngine : : FindSoundByLump ( int lump )
{
if ( lump ! = - 1 )
{
unsigned int i ;
for ( i = 1 ; i < S_sfx . Size ( ) ; i + + )
if ( S_sfx [ i ] . lumpnum = = lump )
return i ;
}
return 0 ;
}
//==========================================================================
//
// S_AddSoundLump
//
// Adds a new sound mapping to S_sfx.
//==========================================================================
2019-12-15 15:32:39 +00:00
int SoundEngine : : AddSoundLump ( const char * logicalname , int lump , int CurrentPitchMask , int resid , int nearlimit )
2019-12-12 18:21:36 +00:00
{
S_sfx . Reserve ( 1 ) ;
sfxinfo_t & newsfx = S_sfx . Last ( ) ;
newsfx . data . Clear ( ) ;
newsfx . name = logicalname ;
newsfx . lumpnum = lump ;
newsfx . next = 0 ;
newsfx . index = 0 ;
newsfx . Volume = 1 ;
newsfx . Attenuation = 1 ;
newsfx . PitchMask = CurrentPitchMask ;
2019-12-15 15:32:39 +00:00
newsfx . NearLimit = nearlimit ;
2019-12-12 18:21:36 +00:00
newsfx . LimitRange = 256 * 256 ;
newsfx . bRandomHeader = false ;
newsfx . bLoadRAW = false ;
newsfx . b16bit = false ;
newsfx . bUsed = false ;
newsfx . bSingular = false ;
newsfx . bTentative = false ;
newsfx . ResourceId = resid ;
newsfx . RawRate = 0 ;
newsfx . link = sfxinfo_t : : NO_LINK ;
newsfx . Rolloff . RolloffType = ROLLOFF_Doom ;
newsfx . Rolloff . MinDistance = 0 ;
newsfx . Rolloff . MaxDistance = 0 ;
newsfx . LoopStart = - 1 ;
if ( resid > = 0 ) ResIdMap [ resid ] = S_sfx . Size ( ) - 1 ;
return ( int ) S_sfx . Size ( ) - 1 ;
}
2019-12-17 18:37:05 +00:00
int SoundEngine : : AddSfx ( sfxinfo_t & sfx )
{
S_sfx . Push ( sfx ) ;
if ( sfx . ResourceId > = 0 ) ResIdMap [ sfx . ResourceId ] = S_sfx . Size ( ) - 1 ;
return ( int ) S_sfx . Size ( ) - 1 ;
}
2019-12-12 18:21:36 +00:00
//==========================================================================
//
// S_FindSoundTentative
//
// Given a logical name, find the sound's index in S_sfx without
// using the hash table. If it does not exist, a new sound without
// an associated lump is created.
//==========================================================================
int SoundEngine : : FindSoundTentative ( const char * name )
{
int id = FindSoundNoHash ( name ) ;
if ( id = = 0 )
{
id = AddSoundLump ( name , - 1 , 0 ) ;
S_sfx [ id ] . bTentative = true ;
}
return id ;
}
//==========================================================================
//
// S_CacheRandomSound
//
// Loads all sounds a random sound might play.
//
//==========================================================================
void SoundEngine : : CacheRandomSound ( sfxinfo_t * sfx )
{
if ( sfx - > bRandomHeader )
{
const FRandomSoundList * list = & S_rnd [ sfx - > link ] ;
for ( unsigned i = 0 ; i < list - > Choices . Size ( ) ; + + i )
{
sfx = & S_sfx [ list - > Choices [ i ] ] ;
sfx - > bUsed = true ;
CacheSound ( & S_sfx [ list - > Choices [ i ] ] ) ;
}
}
}
//==========================================================================
//
// S_GetSoundMSLength
//
// Returns duration of sound
// GZDoom does not use this due to player sound handling
//
//==========================================================================
unsigned int SoundEngine : : GetMSLength ( FSoundID sound )
{
if ( ( unsigned int ) sound > = S_sfx . Size ( ) )
{
return 0 ;
}
sfxinfo_t * sfx = & S_sfx [ sound ] ;
// Resolve player sounds, random sounds, and aliases
if ( sfx - > link ! = sfxinfo_t : : NO_LINK )
{
if ( sfx - > bRandomHeader )
{
// Hm... What should we do here?
// Pick the longest or the shortest sound?
// I think the longest one makes more sense.
int length = 0 ;
const FRandomSoundList * list = & S_rnd [ sfx - > link ] ;
for ( auto & me : list - > Choices )
{
// unfortunately we must load all sounds to find the longest one... :(
int thislen = GetMSLength ( me ) ;
if ( thislen > length ) length = thislen ;
}
return length ;
}
else
{
sfx = & S_sfx [ sfx - > link ] ;
}
}
2020-02-09 12:26:51 +00:00
sfx = LoadSound ( sfx ) ;
2019-12-12 18:21:36 +00:00
if ( sfx ! = NULL ) return GSnd - > GetMSLength ( sfx - > data ) ;
else return 0 ;
}
//==========================================================================
//
// S_PickReplacement
//
// Picks a replacement sound from the associated random list. If this sound
// is not the head of a random list, then the sound passed is returned.
//==========================================================================
int SoundEngine : : PickReplacement ( int refid )
{
while ( S_sfx [ refid ] . bRandomHeader )
{
const FRandomSoundList * list = & S_rnd [ S_sfx [ refid ] . link ] ;
refid = list - > Choices [ rand ( ) % int ( list - > Choices . Size ( ) ) ] ;
}
return refid ;
}
//==========================================================================
//
// S_HashSounds
//
// Fills in the next and index fields of S_sfx to form a working hash table.
//==========================================================================
void SoundEngine : : HashSounds ( )
{
unsigned int i ;
unsigned int j ;
unsigned int size ;
S_sfx . ShrinkToFit ( ) ;
size = S_sfx . Size ( ) ;
// Mark all buckets as empty
for ( i = 0 ; i < size ; i + + )
S_sfx [ i ] . index = 0 ;
// Now set up the chains
for ( i = 1 ; i < size ; i + + )
{
j = MakeKey ( S_sfx [ i ] . name ) % size ;
S_sfx [ i ] . next = S_sfx [ j ] . index ;
S_sfx [ j ] . index = i ;
}
S_rnd . ShrinkToFit ( ) ;
}
void SoundEngine : : AddRandomSound ( int Owner , TArray < uint32_t > list )
{
auto index = S_rnd . Reserve ( 1 ) ;
auto & random = S_rnd . Last ( ) ;
random . Choices = std : : move ( list ) ;
random . Owner = Owner ;
S_sfx [ Owner ] . link = index ;
S_sfx [ Owner ] . bRandomHeader = true ;
S_sfx [ Owner ] . NearLimit = - 1 ;
2019-12-15 19:16:36 +00:00
}
2019-12-22 16:43:39 +00:00
extern ReverbContainer * ForcedEnvironment ;
2020-02-02 19:28:01 +00:00
static int LastReverb ;
CUSTOM_CVAR ( Bool , snd_reverb , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
{
FX_SetReverb ( - 1 ) ;
}
// This is for testing reverb settings.
CUSTOM_CVAR ( Int , snd_reverbtype , - 1 , 0 )
{
FX_SetReverb ( - 1 ) ;
}
2019-12-22 16:43:39 +00:00
void FX_SetReverb ( int strength )
{
2020-02-02 19:28:01 +00:00
if ( strength = = - 1 ) strength = LastReverb ;
if ( snd_reverbtype > - 1 )
{
strength = snd_reverbtype ;
ForcedEnvironment = S_FindEnvironment ( strength ) ;
}
else if ( snd_reverb & & strength > 0 )
2019-12-22 16:43:39 +00:00
{
// todo: optimize environments. The original "reverb" was garbage and not usable as reference.
if ( strength < 64 ) strength = 0x1400 ;
2020-02-02 19:28:01 +00:00
else if ( strength < 192 ) strength = 0x1503 ;
2019-12-22 16:43:39 +00:00
else strength = 0x1900 ;
2020-02-02 19:28:01 +00:00
LastReverb = strength ;
2019-12-22 16:43:39 +00:00
ForcedEnvironment = S_FindEnvironment ( strength ) ;
}
2019-12-28 11:59:19 +00:00
else ForcedEnvironment = nullptr ;
2019-12-22 16:43:39 +00:00
}
2019-12-15 19:16:36 +00:00
# include "basics.h"
# include "stats.h"
# include "v_text.h"
//==========================================================================
//
// S_NoiseDebug
//
// [RH] Print sound debug info. Called by status bar.
//==========================================================================
FString SoundEngine : : NoiseDebug ( )
{
FVector3 listener ;
FVector3 origin ;
2019-12-16 11:32:42 +00:00
listener = this - > listener . position ;
int ch = 0 ;
2019-12-15 19:16:36 +00:00
2020-01-07 00:11:19 +00:00
FString out ;
out . Format ( " *** SOUND DEBUG INFO *** \n Listener: %3.2f %2.3f %2.3f \n "
2019-12-27 19:05:58 +00:00
" x y z vol dist chan pri flags aud pos name \n " , listener . X , listener . Y , listener . Z ) ;
2019-12-15 19:16:36 +00:00
for ( auto chan = Channels ; chan ; chan = chan - > NextChan )
{
2019-12-16 23:29:38 +00:00
if ( ! ( chan - > ChanFlags & CHANF_IS3D ) )
2019-12-15 19:16:36 +00:00
{
out + = " --- --- --- --- " ;
}
else
{
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 ( ) ) ;
}
2020-03-01 09:59:53 +00:00
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 ,
2019-12-27 19:05:58 +00:00
( 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 ,
2020-03-01 09:59:53 +00:00
GSnd - > GetAudibility ( chan ) , GSnd - > GetPosition ( chan ) , ( ( int ) chan - > OrgID ) - 1 , S_sfx [ chan - > SoundID ] . name . GetChars ( ) , chan - > Source ) ;
2019-12-15 19:16:36 +00:00
ch + + ;
}
out . AppendFormat ( " %d channels \n " , ch ) ;
return out ;
}
ADD_STAT ( sounddebug )
{
return soundEngine - > NoiseDebug ( ) ;
}
2020-01-27 21:29:45 +00:00
CVAR ( Bool , snd_extendedlookup , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2019-12-15 19:16:36 +00:00
2020-01-27 21:29:45 +00:00
int S_LookupSound ( const char * fn )
{
2020-04-11 21:54:33 +00:00
static const char * const sndformats [ ] = { " OGG " , " FLAC " , " WAV " } ;
2020-01-27 21:29:45 +00:00
if ( snd_extendedlookup )
{
int lump = fileSystem . FindFileWithExtensions ( StripExtension ( fn ) , sndformats , countof ( sndformats ) ) ;
if ( lump > = 0 ) return lump ;
}
return fileSystem . FindFile ( fn ) ;
}
2020-02-29 11:33:35 +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 )
. Array ( " point " , chan . Point , 3 ) ;
int SourceIndex = 0 ;
if ( arc . isWriting ( ) )
{
if ( chan . SourceType = = SOURCE_Actor ) SourceIndex = int ( ( spritetype * ) ( chan . Source ) - sprite ) ;
else SourceIndex = soundEngine - > SoundSourceIndex ( & chan ) ;
}
arc ( " Source " , SourceIndex ) ;
if ( arc . isReading ( ) )
{
if ( chan . SourceType = = SOURCE_Actor ) chan . Source = & sprite [ SourceIndex ] ;
else soundEngine - > 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 ( ) ;
}
// totalclock runs on 120 fps, we need to allow a small delay here.
soundEngine - > SetRestartTime ( ( int ) totalclock + 6 ) ;
}
GSnd - > Sync ( false ) ;
GSnd - > UpdateSounds ( ) ;
}