2020-06-11 07:22:16 +00:00
//-------------------------------------------------------------------------
/*
2020-05-15 11:14:33 +00:00
Copyright ( C ) 1996 , 2003 - 3 D Realms Entertainment
Copyright ( C ) 2000 , 2003 - Matt Saettler ( EDuke Enhancements )
Copyright ( C ) 2020 - Christoph Oelckers
2020-06-11 07:22:16 +00:00
2020-05-15 11:14:33 +00:00
This file is part of Enhanced Duke Nukem 3 D version 1.5 - Atomic Edition
Duke Nukem 3 D 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 2
of the License , or ( at your option ) any later version .
2020-06-11 07:22:16 +00:00
This program 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 this program ; if not , write to the Free Software
2020-05-15 11:14:33 +00:00
Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
Original Source : 1996 - Todd Replogle
Prepared for public release : 03 / 21 / 2003 - Charlie Wiederhold , 3 D Realms
EDuke enhancements integrated : 04 / 13 / 2003 - Matt Saettler
Note : EDuke source was in transition . Changes are in - progress in the
source as it is released .
2020-06-11 07:22:16 +00:00
*/
//-------------------------------------------------------------------------
2020-05-15 11:14:33 +00:00
2020-06-11 07:22:16 +00:00
# include "ns.h" // Must come before everything else!
2020-08-16 07:46:37 +00:00
# include "g_input.h"
2020-06-11 07:22:16 +00:00
2020-06-21 20:18:12 +00:00
# include "duke3d.h"
2020-11-05 00:14:04 +00:00
# include "dukeactor.h"
2020-06-11 07:22:16 +00:00
# include "raze_music.h"
# include "mapinfo.h"
# include "raze_sound.h"
2020-09-03 21:10:28 +00:00
# include "gamestate.h"
2020-09-27 10:52:26 +00:00
# include "names_d.h"
# include "i_music.h"
2021-04-22 16:52:39 +00:00
# include "vm.h"
2021-05-21 23:34:00 +00:00
# include "s_music.h"
2020-06-11 07:22:16 +00:00
2020-09-11 18:17:36 +00:00
CVAR ( Bool , wt_forcemidi , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // quick hack to disable the oggs, which are of lower quality than playing the MIDIs with a good synth and sound font.
CVAR ( Bool , wt_forcevoc , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // The same for sound effects. The re-recordings are rather poor and disliked
2020-09-26 15:43:34 +00:00
CVAR ( Bool , wt_commentary , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2020-06-11 07:22:16 +00:00
BEGIN_DUKE_NS
2022-10-01 07:16:47 +00:00
int32_t g_cdTrack = - 1 ;
2020-09-26 15:43:34 +00:00
static FSoundID currentCommentarySound ;
2020-07-07 18:27:21 +00:00
2020-11-22 20:14:36 +00:00
void UnmuteSounds ( )
{
soundEngine - > EnumerateChannels ( [ ] ( FSoundChan * chan )
{
if ( chan - > UserData = = 1 )
soundEngine - > SetVolume ( chan , chan - > Volume * 4.f ) ;
chan - > UserData = 0 ;
return 0 ;
} ) ;
}
void MuteSounds ( )
{
soundEngine - > EnumerateChannels ( [ ] ( FSoundChan * chan )
{
if ( chan - > UserData = = 0 )
soundEngine - > SetVolume ( chan , chan - > Volume * 0.25f ) ;
chan - > UserData = 1 ;
return 0 ;
} ) ;
}
2022-12-03 12:57:54 +00:00
void setSpritesetImage ( DDukeActor * self , unsigned int index ) ;
2021-11-28 09:24:42 +00:00
class DukeSoundEngine : public RazeSoundEngine
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
// client specific parts of the sound engine go in this class.
void CalcPosVel ( int type , const void * source , const float pt [ 3 ] , int channum , int chanflags , FSoundID chanSound , FVector3 * pos , FVector3 * vel , FSoundChan * chan ) override ;
TArray < uint8_t > ReadSound ( int lumpnum ) override ;
2020-06-11 07:22:16 +00:00
public :
2020-07-07 18:27:21 +00:00
DukeSoundEngine ( )
{
S_Rolloff . RolloffType = ROLLOFF_Doom ; // Seems like Duke uses the same rolloff type as Doom.
S_Rolloff . MinDistance = 144 ; // was originally 576 which looks like a bug and sounds like crap.
S_Rolloff . MaxDistance = 1088 ;
2022-11-24 20:27:08 +00:00
}
2020-07-07 18:27:21 +00:00
void StopChannel ( FSoundChan * chan ) override
2020-11-22 20:14:36 +00:00
{
2020-07-07 18:27:21 +00:00
if ( chan & & chan - > SysChannel ! = NULL & & ! ( chan - > ChanFlags & CHANF_EVICTED ) & & chan - > SourceType = = SOURCE_Actor )
{
chan - > Source = NULL ;
chan - > SourceType = SOURCE_Unattached ;
}
2020-09-26 15:43:34 +00:00
auto sndid = chan - > SoundID ;
2020-07-07 18:27:21 +00:00
SoundEngine : : StopChannel ( chan ) ;
}
2020-06-11 07:22:16 +00:00
2020-09-26 15:43:34 +00:00
void SoundDone ( FISoundChannel * ichan ) override
{
FSoundChan * schan = static_cast < FSoundChan * > ( ichan ) ;
if ( schan ! = NULL & & schan - > SoundID = = currentCommentarySound )
{
2022-11-24 16:46:39 +00:00
UnloadSound ( schan - > SoundID . index ( ) ) ;
currentCommentarySound = NO_SOUND ;
2022-12-03 12:57:54 +00:00
if ( currentCommentarySprite ) setSpritesetImage ( currentCommentarySprite , 0 ) ;
2020-09-27 10:52:26 +00:00
I_SetRelativeVolume ( 1.0f ) ;
2020-11-22 20:14:36 +00:00
UnmuteSounds ( ) ;
2020-09-26 15:43:34 +00:00
}
SoundEngine : : SoundDone ( schan ) ;
}
2020-06-11 07:22:16 +00:00
} ;
2022-11-24 20:27:08 +00:00
void GameInterface : : StartSoundEngine ( )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
soundEngine = new DukeSoundEngine ;
2020-06-11 07:22:16 +00:00
}
2022-11-24 20:27:08 +00:00
static FSoundID GetReplacementSound ( FSoundID soundNum )
2020-09-11 18:17:36 +00:00
{
2022-11-24 20:27:08 +00:00
if ( wt_forcevoc & & isWorldTour ( ) & & soundEngine - > isValidSoundId ( soundNum ) )
2020-09-11 18:17:36 +00:00
{
2022-11-24 20:27:08 +00:00
auto const * snd = soundEngine - > GetUserData ( soundNum ) ;
2020-09-11 18:17:36 +00:00
int sndx = snd [ kWorldTourMapping ] ;
2022-11-24 20:27:08 +00:00
if ( sndx > 0 ) soundNum = FSoundID : : fromInt ( sndx ) ;
2020-09-11 18:17:36 +00:00
}
return soundNum ;
}
2020-06-11 07:22:16 +00:00
//==========================================================================
//
//
//
//==========================================================================
TArray < uint8_t > DukeSoundEngine : : ReadSound ( int lumpnum )
{
2020-07-07 18:27:21 +00:00
auto wlump = fileSystem . OpenFileReader ( lumpnum ) ;
return wlump . Read ( ) ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2020-07-25 07:32:54 +00:00
void S_CacheAllSounds ( void )
2020-06-11 07:22:16 +00:00
{
2022-11-24 16:46:39 +00:00
for ( unsigned i = 0 ; i < soundEngine - > GetNumSounds ( ) ; i + + )
2020-07-07 18:27:21 +00:00
{
2022-11-24 16:46:39 +00:00
soundEngine - > CacheSound ( FSoundID : : fromInt ( i ) ) ;
if ( ( i & 31 ) = = 0 )
2020-08-16 07:46:37 +00:00
I_GetEvent ( ) ;
2020-07-07 18:27:21 +00:00
}
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2022-11-24 20:27:08 +00:00
static inline int S_GetPitch ( FSoundID soundid )
2020-06-11 07:22:16 +00:00
{
2022-11-24 16:46:39 +00:00
auto const * snd = soundEngine - > GetUserData ( soundid ) ;
2020-07-07 18:27:21 +00:00
if ( ! snd ) return 0 ;
int const range = abs ( snd [ kPitchEnd ] - snd [ kPitchStart ] ) ;
return ( range = = 0 ) ? snd [ kPitchStart ] : min ( snd [ kPitchStart ] , snd [ kPitchEnd ] ) + rand ( ) % range ;
2020-06-11 07:22:16 +00:00
}
float S_ConvertPitch ( int lpitch )
{
2021-05-12 15:33:33 +00:00
return powf ( 2 , lpitch / 1200.f ) ; // I hope I got this right that ASS uses a linear scale where 1200 is a full octave.
2020-06-11 07:22:16 +00:00
}
2022-11-24 20:27:08 +00:00
int S_GetUserFlags ( FSoundID soundid )
2020-06-11 07:22:16 +00:00
{
2022-11-24 16:46:39 +00:00
if ( ! soundEngine - > isValidSoundId ( soundid ) ) return 0 ;
auto const * snd = soundEngine - > GetUserData ( soundid ) ;
2020-07-07 18:27:21 +00:00
if ( ! snd ) return 0 ;
return snd [ kFlags ] ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
int S_DefineSound ( unsigned index , const char * filename , int minpitch , int maxpitch , int priority , int type , int distance , float volume )
{
2022-11-24 22:19:28 +00:00
FSoundID s_index = soundEngine - > FindSoundByResIDNoHash ( index ) ; // cannot use the hash while editing.
2022-11-24 20:27:08 +00:00
if ( ! s_index . isvalid ( ) )
2020-07-07 18:27:21 +00:00
{
2022-11-24 15:49:38 +00:00
// If the slot isn't defined, give it a meaningful name containing the index.
2022-11-24 22:19:28 +00:00
s_index = soundEngine - > FindSoundTentative ( FStringf ( " ConSound@%04d " , index ) ) ;
2020-07-07 18:27:21 +00:00
}
2022-11-24 22:19:28 +00:00
auto sfx = soundEngine - > GetWritableSfx ( s_index ) ;
2022-11-24 15:49:38 +00:00
2022-11-24 22:19:28 +00:00
// Check if we are allowed to override this sound.
// If it is still tentative, it's ok. Otherwise check if SF_CONDEFINED is set, This means it was defined by CON and may be overwritten.
// If the sound was defined by other means we leave it alone, but set the flags to defaults, if they are not present.
bool settable = sfx - > bTentative ;
if ( ! settable )
{
if ( sfx - > UserData . Size ( ) > = kMaxUserData )
{
auto & sndinf = sfx - > UserData ;
settable = ! ! ( sndinf [ kFlags ] & SF_CONDEFINED ) ;
}
}
if ( ! settable )
{
if ( sfx - > UserData . Size ( ) < kMaxUserData )
{
// sound is defined but has no userdata.
// This means it was defined through SNDINFO without setting extended properties and should not be overwritten.
// Set everything to 0 to have default handling.
sfx - > UserData . Resize ( kMaxUserData ) ;
auto & sndinf = sfx - > UserData ;
sndinf [ kPitchStart ] = 0 ;
sndinf [ kPitchEnd ] = 0 ;
sndinf [ kPriority ] = 0 ; // Raze's sound engine does not use this.
sndinf [ kVolAdjust ] = 0 ;
sndinf [ kWorldTourMapping ] = 0 ;
sndinf [ kFlags ] = 0 ;
}
}
sfx - > ResourceId = index ;
2020-07-07 18:27:21 +00:00
sfx - > UserData . Resize ( kMaxUserData ) ;
2022-11-24 22:19:28 +00:00
auto & sndinf = sfx - > UserData ;
sndinf [ kFlags ] = ( type & ~ SF_ONEINST_INTERNAL ) | SF_CONDEFINED ;
2020-07-07 18:27:21 +00:00
if ( sndinf [ kFlags ] & SF_LOOP )
sndinf [ kFlags ] | = SF_ONEINST_INTERNAL ;
2020-09-05 20:01:19 +00:00
// Take care of backslashes in sound names. Also double backslashes which occur in World Tour.
FString fn = filename ;
fn . Substitute ( " \\ \\ " , " \\ " ) ;
FixPathSeperator ( fn ) ;
sfx - > lumpnum = S_LookupSound ( fn ) ;
2020-09-11 19:04:47 +00:00
// For World Tour allow falling back on the classic sounds if the Oggs cannot be found
if ( isWorldTour ( ) & & sfx - > lumpnum = = - 1 )
{
fn . ToLower ( ) ;
fn . Substitute ( " sound/ " , " " ) ;
fn . Substitute ( " .ogg " , " .voc " ) ;
sfx - > lumpnum = S_LookupSound ( fn ) ;
}
2021-10-30 08:35:00 +00:00
sndinf [ kPitchStart ] = clamp < int > ( minpitch , INT16_MIN , INT16_MAX ) ;
sndinf [ kPitchEnd ] = clamp < int > ( maxpitch , INT16_MIN , INT16_MAX ) ;
2020-07-07 18:27:21 +00:00
sndinf [ kPriority ] = priority & 255 ;
2021-10-30 08:35:00 +00:00
sndinf [ kVolAdjust ] = clamp < int > ( distance , INT16_MIN , INT16_MAX ) ;
2020-09-11 19:16:29 +00:00
sndinf [ kWorldTourMapping ] = 0 ;
2020-07-07 18:27:21 +00:00
sfx - > Volume = volume ;
2022-11-24 15:49:38 +00:00
//sfx->NearLimit = index == TELEPORTER + 1? 6 : 0; // the teleporter sound cannot be unlimited due to how it gets used.
2020-07-07 18:27:21 +00:00
sfx - > bTentative = false ;
return 0 ;
2020-06-11 07:22:16 +00:00
}
2020-11-02 21:59:37 +00:00
inline bool S_IsAmbientSFX ( DDukeActor * actor )
2020-05-12 05:51:45 +00:00
{
2022-11-20 07:10:15 +00:00
return ( issoundcontroller ( actor ) & & actor - > spr . lotag < 999 ) ;
2020-05-12 05:51:45 +00:00
}
2020-06-11 07:22:16 +00:00
//==========================================================================
//
//
//
//==========================================================================
2022-11-24 20:27:08 +00:00
static int GetPositionInfo ( DDukeActor * actor , FSoundID soundid , sectortype * sect ,
2022-09-14 16:35:53 +00:00
const DVector3 & cam , const DVector3 & pos , int * distPtr , FVector3 * sndPos )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
// There's a lot of hackery going on here that could be mapped to rolloff and attenuation parameters.
// However, ultimately rolloff would also just reposition the sound source so this can remain as it is.
2021-11-14 14:03:50 +00:00
int orgsndist = 0 , sndist = 0 ;
2022-11-24 16:46:39 +00:00
auto const * snd = soundEngine - > GetUserData ( soundid ) ;
2020-07-07 18:27:21 +00:00
int userflags = snd ? snd [ kFlags ] : 0 ;
int dist_adjust = snd ? snd [ kVolAdjust ] : 0 ;
2020-07-30 22:08:39 +00:00
FVector3 sndorg = GetSoundPos ( pos ) ;
FVector3 campos = GetSoundPos ( cam ) ;
2022-09-01 15:26:46 +00:00
if ( ! actor - > isPlayer ( ) | | actor - > PlayerIndex ( ) ! = screenpeek )
2020-07-07 18:27:21 +00:00
{
2020-07-30 22:08:39 +00:00
orgsndist = sndist = int ( 16 * ( sndorg - campos ) . Length ( ) ) ;
2020-07-07 18:27:21 +00:00
2022-11-20 07:10:15 +00:00
if ( ( userflags & ( SF_GLOBAL | SF_DTAG ) ) ! = SF_GLOBAL & & issoundcontroller ( actor ) & & actor - > spr . lotag < 999 & & ( actor - > sector ( ) - > lotag & 0xff ) < ST_9_SLIDING_ST_DOOR )
2021-12-21 20:35:07 +00:00
sndist = DivScale ( sndist , actor - > spr . hitag + 1 , 14 ) ;
2020-07-07 18:27:21 +00:00
}
sndist + = dist_adjust ;
if ( sndist < 0 ) sndist = 0 ;
2022-11-20 07:10:15 +00:00
if ( sect ! = nullptr & & sndist & & ! issoundcontroller ( actor ) & & ! cansee ( cam . plusZ ( - 24 ) , sect , actor - > spr . pos . plusZ ( - 24 ) , actor - > sector ( ) ) )
2020-07-07 18:27:21 +00:00
sndist + = sndist > > ( isRR ( ) ? 2 : 5 ) ;
// Here the sound distance was clamped to a minimum of 144*4.
// It's better to handle rolloff in the backend instead of whacking the sound origin here.
// That way the lower end can be made customizable instead of losing all precision right here at the source.
if ( sndist < 0 ) sndist = 0 ;
if ( distPtr )
{
* distPtr = sndist ;
}
if ( sndPos )
{
// Now calculate the virtual position in sound system coordinates.
FVector3 sndvec = sndorg - campos ;
if ( orgsndist > 0 )
{
float scale = float ( sndist ) / orgsndist ; // adjust by what was calculated above;
* sndPos = campos + sndvec * scale ;
}
else * sndPos = campos ;
}
return false ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2022-09-14 16:35:53 +00:00
void S_GetCamera ( DVector3 * c , DAngle * ca , sectortype * * cs )
2020-06-11 07:22:16 +00:00
{
2020-11-02 22:53:55 +00:00
if ( ud . cameraactor = = nullptr )
2020-07-07 18:27:21 +00:00
{
auto p = & ps [ screenpeek ] ;
2022-11-27 00:34:39 +00:00
auto pact = p - > GetActor ( ) ;
2022-11-20 13:27:17 +00:00
if ( c )
{
2022-11-27 00:34:39 +00:00
if ( pact ) * c = pact - > getPosWithOffsetZ ( ) ;
2022-11-20 13:27:17 +00:00
else c - > Zero ( ) ;
}
2021-11-21 08:08:05 +00:00
if ( cs ) * cs = p - > cursector ;
2022-11-27 00:34:39 +00:00
if ( ca )
{
if ( pact ) * ca = pact - > spr . Angles . Yaw ;
else * ca = nullAngle ;
}
2020-07-07 18:27:21 +00:00
}
else
{
2022-09-14 16:35:53 +00:00
if ( c ) * c = ud . cameraactor - > spr . pos ;
2021-12-30 15:51:56 +00:00
if ( cs ) * cs = ud . cameraactor - > sector ( ) ;
2022-11-25 12:13:50 +00:00
if ( ca ) * ca = ud . cameraactor - > spr . Angles . Yaw ;
2020-07-07 18:27:21 +00:00
}
2020-06-11 07:22:16 +00:00
}
//=========================================================================
//
// CalcPosVel
//
// The game specific part of the sound updater.
//
//=========================================================================
void DukeSoundEngine : : CalcPosVel ( int type , const void * source , const float pt [ 3 ] , int channum , int chanflags , FSoundID chanSound , FVector3 * pos , FVector3 * vel , FSoundChan * chan )
{
2020-07-07 18:27:21 +00:00
if ( pos ! = nullptr )
{
2022-09-14 16:35:53 +00:00
DVector3 campos ;
2021-11-21 08:08:05 +00:00
sectortype * camsect ;
2020-07-07 18:27:21 +00:00
S_GetCamera ( & campos , nullptr , & camsect ) ;
if ( vel ) vel - > Zero ( ) ;
if ( type = = SOURCE_Unattached )
{
pos - > X = pt [ 0 ] ;
pos - > Y = pt [ 1 ] ;
pos - > Z = pt [ 2 ] ;
}
else if ( type = = SOURCE_Actor )
{
2021-11-28 09:31:15 +00:00
auto aactor = ( DDukeActor * ) source ;
2021-12-21 21:28:13 +00:00
if ( aactor ! = nullptr )
2020-07-07 18:27:21 +00:00
{
2022-11-24 20:27:08 +00:00
GetPositionInfo ( aactor , chanSound , camsect , campos , aactor - > spr . pos , nullptr , pos ) ;
2020-07-07 18:27:21 +00:00
/*
if ( vel ) // DN3D does not properly maintain this.
{
vel - > X = float ( actor - > Vel . X * TICRATE ) ;
vel - > Y = float ( actor - > Vel . Z * TICRATE ) ;
vel - > Z = float ( actor - > Vel . Y * TICRATE ) ;
}
*/
}
}
2022-01-30 22:20:00 +00:00
if ( ( chanflags & CHANF_LISTENERZ ) & & type ! = SOURCE_None )
2020-07-07 18:27:21 +00:00
{
2022-10-12 17:00:43 +00:00
pos - > Y = ( float ) campos . Z / 256.f ;
2020-07-07 18:27:21 +00:00
}
}
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2020-08-29 22:55:49 +00:00
void GameInterface : : UpdateSounds ( void )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
SoundListener listener ;
2022-09-14 16:35:53 +00:00
DVector3 c ;
DAngle ca ;
2021-11-21 08:08:05 +00:00
sectortype * cs ;
2021-12-30 09:30:21 +00:00
2020-09-03 21:10:28 +00:00
if ( isRR ( ) & & ! Mus_IsPlaying ( ) & & ! paused & & gamestate = = GS_LEVEL )
2020-07-07 18:27:21 +00:00
S_PlayRRMusic ( ) ;
S_GetCamera ( & c , & ca , & cs ) ;
2022-09-14 16:35:53 +00:00
listener . angle = - float ( ca . Radians ( ) ) ;
2022-01-30 22:20:00 +00:00
listener . velocity . Zero ( ) ;
listener . position = GetSoundPos ( c ) ;
listener . underwater = false ;
// This should probably use a real environment instead of the pitch hacking in S_PlaySound3D.
// listenactor->waterlevel == 3;
//assert(primaryLevel->Zones.Size() > listenactor->Sector->ZoneNumber);
listener . Environment = 0 ; // primaryLevel->Zones[listenactor->Sector->ZoneNumber].Environment;
listener . valid = true ;
2021-12-22 09:01:16 +00:00
listener . ListenerObject = ud . cameraactor = = nullptr ? nullptr : ud . cameraactor . Get ( ) ;
2020-07-07 18:27:21 +00:00
soundEngine - > SetListener ( listener ) ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2022-11-24 20:27:08 +00:00
int S_PlaySound3D ( FSoundID soundid , DDukeActor * actor , const DVector3 & pos , int channel , EChanFlags flags )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
auto const pl = & ps [ myconnectindex ] ;
2022-11-24 16:46:39 +00:00
if ( ! soundEngine - > isValidSoundId ( soundid ) | | ! SoundEnabled ( ) | | actor = = nullptr | | ! playrunning ( ) | |
2020-07-07 18:27:21 +00:00
( pl - > timebeforeexit > 0 & & pl - > timebeforeexit < = REALGAMETICSPERSEC * 3 ) ) return - 1 ;
2022-11-24 20:27:08 +00:00
soundid = GetReplacementSound ( soundid ) ;
int userflags = S_GetUserFlags ( soundid ) ;
2020-07-07 18:27:21 +00:00
if ( ( userflags & ( SF_DTAG | SF_GLOBAL ) ) = = SF_DTAG )
{
// Duke-Tag sound does not play in 3D.
2022-11-24 20:27:08 +00:00
return S_PlaySound ( soundid ) ;
2020-07-07 18:27:21 +00:00
}
if ( userflags & SF_TALK )
{
2022-09-01 15:26:46 +00:00
if ( snd_speech = = 0 | | ( ud . multimode > 1 & & actor - > isPlayer ( ) & & actor - > PlayerIndex ( ) ! = screenpeek & & ud . coop ! = 1 ) ) return - 1 ;
2020-07-07 18:27:21 +00:00
bool foundone = soundEngine - > EnumerateChannels ( [ & ] ( FSoundChan * chan )
{
auto sid = chan - > OrgID ;
2022-11-24 20:27:08 +00:00
auto flags = S_GetUserFlags ( sid ) ;
2020-07-07 18:27:21 +00:00
return ! ! ( flags & SF_TALK ) ;
} ) ;
// don't play if any Duke talk sounds are already playing
if ( foundone ) return - 1 ;
2020-07-29 20:26:14 +00:00
// When in single player, force all talk sounds to originate from the player actor, no matter what is being used to start them.
// Fixes a problem with quake06.voc in E3L4.
if ( ud . multimode = = 1 )
{
2020-11-02 21:59:37 +00:00
actor = pl - > GetActor ( ) ;
2020-07-29 20:26:14 +00:00
}
2020-07-07 18:27:21 +00:00
}
2020-07-20 21:21:27 +00:00
int32_t sndist ;
2020-07-07 18:27:21 +00:00
FVector3 sndpos ; // this is in sound engine space.
2022-09-14 16:35:53 +00:00
DVector3 campos ;
2021-11-21 08:08:05 +00:00
sectortype * camsect ;
2020-07-07 18:27:21 +00:00
S_GetCamera ( & campos , nullptr , & camsect ) ;
2022-11-24 20:27:08 +00:00
GetPositionInfo ( actor , soundid , camsect , campos , pos , & sndist , & sndpos ) ;
int pitch = S_GetPitch ( soundid ) ;
2020-07-07 18:27:21 +00:00
2022-11-24 20:27:08 +00:00
auto sfx = soundEngine - > GetSfx ( soundid ) ;
bool explosion = ( ( userflags & ( SF_GLOBAL | SF_DTAG ) ) = = ( SF_GLOBAL | SF_DTAG ) ) | |
( ( sfx - > ResourceId = = PIPEBOMB_EXPLODE | | sfx - > ResourceId = = LASERTRIP_EXPLODE | | sfx - > ResourceId = = RPG_EXPLODE ) ) ;
2020-07-07 18:27:21 +00:00
2021-11-21 07:56:39 +00:00
bool underwater = ps [ screenpeek ] . insector ( ) & & ps [ screenpeek ] . cursector - > lotag = = ST_2_UNDERWATER ;
2020-07-07 18:27:21 +00:00
if ( explosion )
{
if ( underwater )
pitch - = 1024 ;
}
else
{
2022-11-20 07:10:15 +00:00
if ( sndist > 32767 & & ! issoundcontroller ( actor ) & & ( userflags & ( SF_LOOP | SF_MSFX ) ) = = 0 )
2020-07-07 18:27:21 +00:00
return - 1 ;
if ( underwater & & ( userflags & SF_TALK ) = = 0 )
pitch = - 768 ;
}
2022-11-24 16:46:39 +00:00
bool is_playing = soundEngine - > GetSoundPlayingInfo ( SOURCE_Any , nullptr , soundid ) ;
2022-11-20 07:10:15 +00:00
if ( is_playing & & ! issoundcontroller ( actor ) )
2022-11-24 20:27:08 +00:00
S_StopSound ( soundid , actor ) ;
2020-07-07 18:27:21 +00:00
int const repeatp = ( userflags & SF_LOOP ) ;
if ( repeatp & & ( userflags & SF_ONEINST_INTERNAL ) & & is_playing )
{
return - 1 ;
}
// These explosion sounds originally used some distance hackery to make them louder but due to how the rolloff was set up they always played at full volume as a result.
// I think it is better to lower their attenuation so that they are louder than the rest but still fade in the distance.
// For the original effect, attenuation needs to be set to ATTN_NONE here.
float attenuation ;
if ( explosion ) attenuation = 0.5f ;
else attenuation = ( userflags & ( SF_GLOBAL | SF_DTAG ) ) = = SF_GLOBAL ? ATTN_NONE : ATTN_NORM ;
if ( userflags & SF_LOOP ) flags | = CHANF_LOOP ;
2020-11-22 20:14:36 +00:00
float vol = attenuation = = ATTN_NONE ? 0.8f : 1.f ;
2022-11-24 16:46:39 +00:00
if ( currentCommentarySound ! = NO_SOUND ) vol * = 0.25f ;
auto chan = soundEngine - > StartSound ( SOURCE_Actor , actor , & sndpos , CHAN_AUTO , flags , soundid , vol , attenuation , nullptr , S_ConvertPitch ( pitch ) ) ;
if ( chan ) chan - > UserData = ( currentCommentarySound ! = NO_SOUND ) ;
2020-07-07 18:27:21 +00:00
return chan ? 0 : - 1 ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2022-11-24 20:27:08 +00:00
int S_PlaySound ( FSoundID soundid , int channel , EChanFlags flags , float vol )
2020-06-11 07:22:16 +00:00
{
2022-11-24 16:46:39 +00:00
if ( ! soundEngine - > isValidSoundId ( soundid ) | | ! SoundEnabled ( ) ) return - 1 ;
2021-12-30 09:30:21 +00:00
2022-11-24 20:27:08 +00:00
soundid = GetReplacementSound ( soundid ) ;
2020-06-11 07:22:16 +00:00
2022-11-24 20:27:08 +00:00
int userflags = S_GetUserFlags ( soundid ) ;
2020-07-29 20:40:29 +00:00
if ( ( ! ( snd_speech & 1 ) & & ( userflags & SF_TALK ) ) )
2020-07-07 18:27:21 +00:00
return - 1 ;
2020-06-11 07:22:16 +00:00
2022-11-24 20:27:08 +00:00
int const pitch = S_GetPitch ( soundid ) ;
2020-06-11 07:22:16 +00:00
2020-07-07 18:27:21 +00:00
if ( userflags & SF_LOOP ) flags | = CHANF_LOOP ;
2022-11-24 16:46:39 +00:00
if ( currentCommentarySound ! = NO_SOUND ) vol * = 0.25f ;
auto chan = soundEngine - > StartSound ( SOURCE_None , nullptr , nullptr , channel , flags , soundid , vol , ATTN_NONE , nullptr , S_ConvertPitch ( pitch ) ) ;
if ( chan ) chan - > UserData = ( currentCommentarySound ! = NO_SOUND ) ;
2020-07-07 18:27:21 +00:00
return chan ? 0 : - 1 ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2022-11-24 20:27:08 +00:00
int S_PlayActorSound ( FSoundID soundNum , DDukeActor * actor , int channel , EChanFlags flags )
2020-06-11 07:22:16 +00:00
{
2020-11-02 21:59:37 +00:00
return ( actor = = nullptr ? S_PlaySound ( soundNum , channel , flags ) :
2022-09-13 17:49:25 +00:00
S_PlaySound3D ( soundNum , actor , actor - > spr . pos , channel , flags ) ) ;
2020-06-11 07:22:16 +00:00
}
2022-11-24 20:27:08 +00:00
void S_StopSound ( FSoundID soundid , DDukeActor * actor , int channel )
2020-06-11 07:22:16 +00:00
{
2022-11-24 20:27:08 +00:00
soundid = GetReplacementSound ( soundid ) ;
2020-09-11 18:17:36 +00:00
2022-11-24 16:46:39 +00:00
if ( ! actor ) soundEngine - > StopSoundID ( soundid ) ;
2020-07-07 18:27:21 +00:00
else
{
2022-11-24 16:46:39 +00:00
if ( channel = = - 1 ) soundEngine - > StopSound ( SOURCE_Actor , actor , - 1 , soundid ) ;
else soundEngine - > StopSound ( SOURCE_Actor , actor , channel ) ;
2020-07-07 18:27:21 +00:00
// StopSound kills the actor reference so this cannot be delayed until ChannelEnded gets called. At that point the actor may also not be valid anymore.
2021-12-30 15:51:56 +00:00
if ( S_IsAmbientSFX ( actor ) & & actor - > sector ( ) - > lotag < 3 ) // ST_2_UNDERWATER
2020-11-02 21:59:37 +00:00
actor - > temp_data [ 0 ] = 0 ;
2020-07-07 18:27:21 +00:00
}
2020-06-11 07:22:16 +00:00
}
2022-11-24 20:27:08 +00:00
void S_ChangeSoundPitch ( FSoundID soundid , DDukeActor * actor , int pitchoffset )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
double expitch = pow ( 2 , pitchoffset / 1200. ) ; // I hope I got this right that ASS uses a linear scale where 1200 is a full octave.
2020-11-02 21:59:37 +00:00
if ( ! actor )
2020-07-07 18:27:21 +00:00
{
2022-11-24 16:46:39 +00:00
soundEngine - > ChangeSoundPitch ( SOURCE_Unattached , nullptr , CHAN_AUTO , expitch , soundid ) ;
2020-07-07 18:27:21 +00:00
}
else
{
2022-11-24 16:46:39 +00:00
soundEngine - > ChangeSoundPitch ( SOURCE_Actor , actor , CHAN_AUTO , expitch , soundid ) ;
2020-07-07 18:27:21 +00:00
}
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2022-11-24 20:27:08 +00:00
int S_CheckActorSoundPlaying ( DDukeActor * actor , FSoundID soundid , int channel )
2020-06-11 07:22:16 +00:00
{
2022-11-24 20:27:08 +00:00
soundid = GetReplacementSound ( soundid ) ;
2020-09-11 18:17:36 +00:00
2022-11-24 16:46:39 +00:00
if ( actor = = nullptr ) return soundEngine - > GetSoundPlayingInfo ( SOURCE_Any , nullptr , soundid ) ;
return soundEngine - > IsSourcePlayingSomething ( SOURCE_Actor , actor , channel , soundid ) ;
2020-06-11 07:22:16 +00:00
}
// Check if actor <i> is playing any sound.
2020-10-25 05:40:05 +00:00
int S_CheckAnyActorSoundPlaying ( DDukeActor * actor )
2020-06-11 07:22:16 +00:00
{
2020-10-25 05:40:05 +00:00
if ( ! actor ) return false ;
2022-11-24 16:46:39 +00:00
return soundEngine - > IsSourcePlayingSomething ( SOURCE_Actor , actor , CHAN_AUTO ) ;
2020-06-11 07:22:16 +00:00
}
2022-11-24 20:27:08 +00:00
int S_CheckSoundPlaying ( FSoundID soundid )
2020-06-11 07:22:16 +00:00
{
2022-11-24 16:46:39 +00:00
return soundEngine - > GetSoundPlayingInfo ( SOURCE_Any , nullptr , soundid ) ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
void S_MenuSound ( void )
{
2020-07-07 18:27:21 +00:00
static int menunum ;
2021-11-07 13:55:28 +00:00
static const uint16_t menusnds [ ] =
2020-07-07 18:27:21 +00:00
{
LASERTRIP_EXPLODE ,
DUKE_GRUNT ,
DUKE_LAND_HURT ,
CHAINGUN_FIRE ,
SQUISHED ,
KICK_HIT ,
PISTOL_RICOCHET ,
PISTOL_BODYHIT ,
PISTOL_FIRE ,
SHOTGUN_FIRE ,
BOS1_WALK ,
RPG_EXPLODE ,
PIPEBOMB_BOUNCE ,
PIPEBOMB_EXPLODE ,
NITEVISION_ONOFF ,
RPG_SHOOT ,
SELECT_WEAPON
} ;
int s = isRR ( ) ? 390 : menusnds [ menunum + + % countof ( menusnds ) ] ;
if ( s ! = - 1 )
S_PlaySound ( s , CHAN_AUTO , CHANF_UI ) ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
// Music
//
//==========================================================================
static bool cd_disabled = false ; // This is in case mus_redbook is enabled but no tracks found so that the regular music system can be switched on.
2021-05-11 23:58:42 +00:00
static void MusPlay ( const char * music , bool loop )
2020-09-07 19:26:07 +00:00
{
if ( isWorldTour ( ) )
{
if ( wt_forcemidi )
{
FString alternative = music ;
alternative . Substitute ( " .ogg " , " .mid " ) ;
int num = fileSystem . FindFile ( alternative ) ;
if ( num > = 0 )
{
int file = fileSystem . GetFileContainer ( num ) ;
if ( file = = 1 )
{
2021-05-11 23:58:42 +00:00
Mus_Play ( alternative , loop ) ;
2020-09-07 19:26:07 +00:00
return ;
}
}
}
}
2021-05-11 23:58:42 +00:00
int result = Mus_Play ( music , loop ) ;
2020-09-07 19:26:07 +00:00
// do not remain silent if playing World Tour when the user has deleted the music.
if ( ! result & & isWorldTour ( ) )
{
FString alternative = music ;
alternative . Substitute ( " .ogg " , " .mid " ) ;
2021-05-11 23:58:42 +00:00
Mus_Play ( alternative , loop ) ;
2020-09-07 19:26:07 +00:00
}
}
2020-07-07 11:19:09 +00:00
void S_PlayLevelMusic ( MapRecord * mi )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
if ( isRR ( ) & & mi - > music . IsEmpty ( ) & & mus_redbook & & ! cd_disabled ) return ;
2021-05-11 23:58:42 +00:00
MusPlay ( mi - > music , true ) ;
2020-06-11 07:22:16 +00:00
}
2020-05-15 11:14:33 +00:00
void S_PlaySpecialMusic ( unsigned int m )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
if ( isRR ( ) | | m > = specialmusic . Size ( ) ) return ; // Can only be MUS_LOADING, isRR() does not use it.
auto & musicfn = specialmusic [ m ] ;
if ( musicfn . IsNotEmpty ( ) )
{
2021-05-11 23:58:42 +00:00
MusPlay ( musicfn , true ) ;
2020-07-07 18:27:21 +00:00
}
2020-06-11 07:22:16 +00:00
}
2020-05-15 11:14:33 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-06-11 07:22:16 +00:00
void S_PlayRRMusic ( int newTrack )
{
2020-07-07 18:27:21 +00:00
if ( ! isRR ( ) | | ! mus_redbook | | cd_disabled | | currentLevel - > music . IsNotEmpty ( ) )
return ;
Mus_Stop ( ) ;
for ( int i = 0 ; i < 10 ; i + + )
{
g_cdTrack = newTrack ! = - 1 ? newTrack : g_cdTrack + 1 ;
if ( newTrack ! = 10 & & ( g_cdTrack > 9 | | g_cdTrack < 2 ) )
g_cdTrack = 2 ;
2021-03-12 22:15:34 +00:00
FStringf filename ( " redneck%s%02d.ogg " , isRRRA ( ) ? " rides " : " " , g_cdTrack ) ;
2021-05-11 23:58:42 +00:00
if ( Mus_Play ( filename , false ) ) return ;
2021-03-12 22:15:34 +00:00
filename . Format ( " track%02d.ogg " , g_cdTrack ) ;
2021-05-11 23:58:42 +00:00
if ( Mus_Play ( filename , false ) ) return ;
2020-07-07 18:27:21 +00:00
}
// If none of the tracks managed to start, disable the CD music for this session so that regular music can play if defined.
cd_disabled = true ;
2020-06-11 07:22:16 +00:00
}
2020-07-25 07:32:54 +00:00
void S_PlayBonusMusic ( )
2020-07-01 10:55:32 +00:00
{
2020-07-07 18:27:21 +00:00
if ( MusicEnabled ( ) & & mus_enabled )
S_PlaySound ( BONUSMUSIC , CHAN_AUTO , CHANF_UI ) ;
2020-07-01 10:55:32 +00:00
}
2020-09-11 18:17:36 +00:00
void S_WorldTourMappingsForOldSounds ( )
{
// This tries to retrieve the original sounds for World Tour's often inferior replacements.
// It's really ironic that despite their low quality they often sound a lot better than the new ones.
if ( ! isWorldTour ( ) ) return ;
2022-11-24 16:46:39 +00:00
unsigned maxsnd = soundEngine - > GetNumSounds ( ) ;
for ( unsigned i = 1 ; i < maxsnd ; i + + )
2020-09-11 18:17:36 +00:00
{
2022-11-24 16:46:39 +00:00
auto sfx = soundEngine - > GetSfx ( FSoundID : : fromInt ( i ) ) ;
2022-12-11 21:15:59 +00:00
FString fname = fileSystem . GetFileFullName ( sfx - > lumpnum ) ;
2020-09-11 18:17:36 +00:00
if ( ! fname . Right ( 4 ) . CompareNoCase ( " .ogg " ) )
{
// All names here follow the same convention. We must strip the "sound/" folder and replace the extension to get the original VOCs.
fname . ToLower ( ) ;
fname . Substitute ( " sound/ " , " " ) ;
fname . Substitute ( " .ogg " , " .voc " ) ;
int lump = fileSystem . FindFile ( fname ) ; // in this case we absolutely do not want the extended lookup that's optionally performed by S_LookupSound.
if ( lump > = 0 )
{
2022-11-24 16:46:39 +00:00
auto newsfx = soundEngine - > AllocateSound ( ) ;
* newsfx = * sfx ;
newsfx - > name = fname ;
newsfx - > lumpnum = lump ;
sfx - > UserData [ kWorldTourMapping ] = soundEngine - > GetNumSounds ( ) - 1 ;
2020-09-11 18:17:36 +00:00
}
}
}
}
2020-09-26 14:18:44 +00:00
static TArray < FString > Commentaries ;
void S_ParseDeveloperCommentary ( )
{
int lumpnum = fileSystem . FindFile ( " def/developer_commentary.def " ) ;
if ( lumpnum < 0 ) return ;
FScanner sc ;
sc . OpenLumpNum ( lumpnum ) ;
try
{
sc . SetCMode ( true ) ;
sc . MustGetStringName ( " def " ) ;
sc . MustGetStringName ( " developercommentary " ) ;
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
FString path ;
int num = - 1 ;
sc . MustGetStringName ( " def " ) ;
sc . MustGetStringName ( " sound " ) ;
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
sc . MustGetString ( ) ;
if ( sc . Compare ( " path " ) )
{
sc . MustGetStringName ( " : " ) ;
sc . MustGetString ( ) ;
path = sc . String ;
sc . MustGetStringName ( " ; " ) ;
}
else if ( sc . Compare ( " num " ) )
{
sc . MustGetStringName ( " : " ) ;
sc . MustGetNumber ( ) ;
num = sc . Number ;
sc . MustGetStringName ( " ; " ) ;
}
}
sc . MustGetStringName ( " ; " ) ;
2021-05-12 15:33:33 +00:00
if ( Commentaries . Size ( ) < = ( unsigned ) num ) Commentaries . Resize ( num + 1 ) ;
2020-09-26 14:18:44 +00:00
Commentaries [ num ] = std : : move ( path ) ;
}
//sc.MustGetStringName(";");
}
catch ( const std : : exception & ex )
{
Printf ( " Failed to read developer commentary definitions: \n %s " , ex . what ( ) ) ;
return ;
}
}
2020-09-26 15:43:34 +00:00
void StopCommentary ( )
{
2022-11-24 16:46:39 +00:00
if ( currentCommentarySound . isvalid ( ) )
2020-09-26 15:43:34 +00:00
{
soundEngine - > StopSound ( SOURCE_None , nullptr , CHAN_VOICE , currentCommentarySound ) ;
}
}
2022-12-03 12:57:54 +00:00
int StartCommentary ( int tag , DDukeActor * actor )
2020-09-26 15:43:34 +00:00
{
2021-05-12 15:33:33 +00:00
if ( wt_commentary & & Commentaries . Size ( ) > ( unsigned ) tag & & Commentaries [ tag ] . IsNotEmpty ( ) )
2020-09-26 15:43:34 +00:00
{
FSoundID id = soundEngine - > FindSound ( Commentaries [ tag ] ) ;
2022-11-24 16:46:39 +00:00
if ( ! id . isvalid ( ) )
2020-09-26 15:43:34 +00:00
{
int lump = fileSystem . FindFile ( Commentaries [ tag ] ) ;
if ( lump < 0 )
{
Commentaries [ tag ] = " " ;
return false ;
}
id = FSoundID ( soundEngine - > AddSoundLump ( Commentaries [ tag ] , lump , 0 ) ) ;
}
StopCommentary ( ) ;
2020-11-22 20:14:36 +00:00
MuteSounds ( ) ;
2020-09-26 15:43:34 +00:00
soundEngine - > StartSound ( SOURCE_None , nullptr , nullptr , CHAN_VOICE , CHANF_UI | CHANF_TRANSIENT | CHANF_OVERLAP , id , 1.f , 0.f ) ;
currentCommentarySound = id ;
2020-10-26 06:30:34 +00:00
currentCommentarySprite = actor ;
2020-09-27 10:52:26 +00:00
I_SetRelativeVolume ( 0.25f ) ;
2020-09-26 15:43:34 +00:00
return true ;
}
return false ;
}
2020-06-11 07:22:16 +00:00
END_DUKE_NS