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
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 ;
} ) ;
}
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 ;
}
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 )
{
UnloadSound ( schan - > SoundID ) ;
currentCommentarySound = 0 ;
2021-12-21 17:19:45 +00:00
if ( currentCommentarySprite ) currentCommentarySprite - > spr . picnum = DEVELOPERCOMMENTARY ;
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
} ;
void S_InitSound ( )
{
2020-07-07 18:27:21 +00:00
soundEngine = new DukeSoundEngine ;
2020-06-11 07:22:16 +00:00
}
2020-09-11 18:17:36 +00:00
static int GetReplacementSound ( int soundNum )
{
if ( wt_forcevoc & & isWorldTour ( ) & & soundEngine - > isValidSoundId ( soundNum + 1 ) )
{
auto const * snd = soundEngine - > GetUserData ( soundNum + 1 ) ;
int sndx = snd [ kWorldTourMapping ] ;
if ( sndx > 0 ) soundNum = sndx - 1 ;
}
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
{
2020-07-07 18:27:21 +00:00
auto & sfx = soundEngine - > GetSounds ( ) ;
int i = 0 ;
for ( auto & snd : sfx )
{
soundEngine - > CacheSound ( & snd ) ;
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
}
//==========================================================================
//
//
//
//==========================================================================
static inline int S_GetPitch ( int num )
{
2020-07-07 18:27:21 +00:00
auto const * snd = soundEngine - > GetUserData ( num + 1 ) ;
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
}
int S_GetUserFlags ( int num )
{
2020-07-07 18:27:21 +00:00
if ( ! soundEngine - > isValidSoundId ( num + 1 ) ) return 0 ;
auto const * snd = soundEngine - > GetUserData ( num + 1 ) ;
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 )
{
2020-07-07 18:27:21 +00:00
auto & S_sfx = soundEngine - > GetSounds ( ) ;
index + + ;
if ( index > = S_sfx . Size ( ) )
{
S_sfx . Resize ( index + 1 ) ;
}
auto sfx = & S_sfx [ index ] ;
sfx - > UserData . Resize ( kMaxUserData ) ;
auto sndinf = sfx - > UserData . Data ( ) ;
sndinf [ kFlags ] = type & ~ SF_ONEINST_INTERNAL ;
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-01-09 14:18:35 +00:00
sfx - > NearLimit = 0 ;
2020-07-07 18:27:21 +00:00
sfx - > bTentative = false ;
2020-09-05 20:01:19 +00:00
sfx - > name = std : : move ( fn ) ;
2020-07-07 18:27:21 +00:00
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
{
2021-12-21 17:19:45 +00:00
return ( actor - > spr . picnum = = MUSICANDSFX & & actor - > spr . lotag < 999 ) ;
2020-05-12 05:51:45 +00:00
}
2020-06-11 07:22:16 +00:00
//==========================================================================
//
//
//
//==========================================================================
2021-11-21 08:08:05 +00:00
static int GetPositionInfo ( DDukeActor * actor , int soundNum , sectortype * sect ,
2020-07-07 18:27:21 +00:00
const vec3_t * cam , const vec3_t * 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 ;
2020-07-07 18:27:21 +00:00
auto const * snd = soundEngine - > GetUserData ( soundNum + 1 ) ;
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 ) ;
2021-12-31 17:47:00 +00:00
if ( ! actor - > isPlayer ( ) | | actor - > spr . yvel ! = 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
2021-12-30 15:51:56 +00:00
if ( ( userflags & ( SF_GLOBAL | SF_DTAG ) ) ! = SF_GLOBAL & & actor - > spr . picnum = = MUSICANDSFX & & 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 ;
2021-12-30 15:51:56 +00:00
if ( sect ! = nullptr & & sndist & & actor - > spr . picnum ! = MUSICANDSFX & & ! cansee ( cam - > X , cam - > Y , cam - > Z - ( 24 < < 8 ) , sect , actor - > spr . pos . X , actor - > spr . pos . Y , actor - > spr . pos . Z - ( 24 < < 8 ) , 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
}
//==========================================================================
//
//
//
//==========================================================================
2021-11-21 08:08:05 +00:00
void S_GetCamera ( vec3_t * * c , int32_t * 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 ] ;
if ( c ) * c = & p - > pos ;
2021-11-21 08:08:05 +00:00
if ( cs ) * cs = p - > cursector ;
2020-10-07 12:13:21 +00:00
if ( ca ) * ca = p - > angle . ang . asbuild ( ) ;
2020-07-07 18:27:21 +00:00
}
else
{
2021-12-21 17:19:45 +00:00
if ( c ) * c = & ud . cameraactor - > spr . pos ;
2021-12-30 15:51:56 +00:00
if ( cs ) * cs = ud . cameraactor - > sector ( ) ;
2021-12-21 17:19:45 +00:00
if ( ca ) * ca = ud . cameraactor - > spr . ang ;
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 )
{
vec3_t * 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
{
2021-12-21 21:28:13 +00:00
GetPositionInfo ( aactor , chanSound - 1 , 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 ) ;
}
*/
}
}
if ( ( chanflags & CHANF_LISTENERZ ) & & campos ! = nullptr & & type ! = SOURCE_None )
{
2021-12-22 09:41:47 +00:00
pos - > Y = 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 ;
vec3_t * c ;
2021-11-21 08:08:05 +00:00
int32_t ca ;
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 ) ;
if ( c ! = nullptr )
{
2021-05-12 15:33:33 +00:00
listener . angle = - float ( ca * BAngRadian ) ; // Build uses a period of 2048.
2020-07-07 18:27:21 +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 ;
}
else
{
listener . angle = 0 ;
listener . position . Zero ( ) ;
listener . velocity . Zero ( ) ;
listener . underwater = false ;
listener . Environment = nullptr ;
listener . valid = false ;
}
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
}
//==========================================================================
//
//
//
//==========================================================================
2020-11-02 21:59:37 +00:00
int S_PlaySound3D ( int sndnum , DDukeActor * actor , const vec3_t * 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 ] ;
2020-11-02 21:59:37 +00:00
if ( ! soundEngine - > isValidSoundId ( sndnum + 1 ) | | ! SoundEnabled ( ) | | actor = = nullptr | | ! playrunning ( ) | |
2020-07-07 18:27:21 +00:00
( pl - > timebeforeexit > 0 & & pl - > timebeforeexit < = REALGAMETICSPERSEC * 3 ) ) return - 1 ;
2020-09-11 18:17:36 +00:00
sndnum = GetReplacementSound ( sndnum ) ;
2020-07-07 18:27:21 +00:00
int userflags = S_GetUserFlags ( sndnum ) ;
if ( ( userflags & ( SF_DTAG | SF_GLOBAL ) ) = = SF_DTAG )
{
// Duke-Tag sound does not play in 3D.
return S_PlaySound ( sndnum ) ;
}
if ( userflags & SF_TALK )
{
2021-12-31 17:47:00 +00:00
if ( snd_speech = = 0 | | ( ud . multimode > 1 & & actor - > isPlayer ( ) & & actor - > spr . yvel ! = screenpeek & & ud . coop ! = 1 ) ) return - 1 ;
2020-07-07 18:27:21 +00:00
bool foundone = soundEngine - > EnumerateChannels ( [ & ] ( FSoundChan * chan )
{
auto sid = chan - > OrgID ;
auto flags = S_GetUserFlags ( sid - 1 ) ;
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.
vec3_t * campos ;
2021-11-21 08:08:05 +00:00
sectortype * camsect ;
2020-07-07 18:27:21 +00:00
S_GetCamera ( & campos , nullptr , & camsect ) ;
2020-11-02 21:59:37 +00:00
GetPositionInfo ( actor , sndnum , camsect , campos , pos , & sndist , & sndpos ) ;
2020-07-20 21:21:27 +00:00
int pitch = S_GetPitch ( sndnum ) ;
2020-07-07 18:27:21 +00:00
bool explosion = ( ( userflags & ( SF_GLOBAL | SF_DTAG ) ) = = ( SF_GLOBAL | SF_DTAG ) ) | | ( ( sndnum = = PIPEBOMB_EXPLODE | | sndnum = = LASERTRIP_EXPLODE | | sndnum = = RPG_EXPLODE ) ) ;
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
{
2021-12-21 17:19:45 +00:00
if ( sndist > 32767 & & actor - > spr . picnum ! = MUSICANDSFX & & ( userflags & ( SF_LOOP | SF_MSFX ) ) = = 0 )
2020-07-07 18:27:21 +00:00
return - 1 ;
if ( underwater & & ( userflags & SF_TALK ) = = 0 )
pitch = - 768 ;
}
bool is_playing = soundEngine - > GetSoundPlayingInfo ( SOURCE_Any , nullptr , sndnum + 1 ) ;
2021-12-21 17:19:45 +00:00
if ( is_playing & & actor - > spr . picnum ! = MUSICANDSFX )
2020-11-02 21:59:37 +00:00
S_StopSound ( sndnum , 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 ;
if ( currentCommentarySound ! = 0 ) vol * = 0.25f ;
2021-11-28 09:31:15 +00:00
auto chan = soundEngine - > StartSound ( SOURCE_Actor , actor , & sndpos , CHAN_AUTO , flags , sndnum + 1 , vol , attenuation , nullptr , S_ConvertPitch ( pitch ) ) ;
2020-11-22 20:14:36 +00:00
if ( chan ) chan - > UserData = ( currentCommentarySound ! = 0 ) ;
2020-07-07 18:27:21 +00:00
return chan ? 0 : - 1 ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2020-08-10 18:59:04 +00:00
int S_PlaySound ( int sndnum , int channel , EChanFlags flags , float vol )
2020-06-11 07:22:16 +00:00
{
2020-07-07 18:27:21 +00:00
if ( ! soundEngine - > isValidSoundId ( sndnum + 1 ) | | ! SoundEnabled ( ) ) return - 1 ;
2021-12-30 09:30:21 +00:00
2020-09-11 18:17:36 +00:00
sndnum = GetReplacementSound ( sndnum ) ;
2020-06-11 07:22:16 +00:00
2020-07-07 18:27:21 +00:00
int userflags = S_GetUserFlags ( sndnum ) ;
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
2020-07-07 18:27:21 +00:00
int const pitch = S_GetPitch ( sndnum ) ;
2020-06-11 07:22:16 +00:00
2020-07-07 18:27:21 +00:00
if ( userflags & SF_LOOP ) flags | = CHANF_LOOP ;
2020-11-22 20:14:36 +00:00
if ( currentCommentarySound ! = 0 ) vol * = 0.25f ;
2020-08-10 18:59:04 +00:00
auto chan = soundEngine - > StartSound ( SOURCE_None , nullptr , nullptr , channel , flags , sndnum + 1 , vol , ATTN_NONE , nullptr , S_ConvertPitch ( pitch ) ) ;
2020-11-22 20:14:36 +00:00
if ( chan ) chan - > UserData = ( currentCommentarySound ! = 0 ) ;
2020-07-07 18:27:21 +00:00
return chan ? 0 : - 1 ;
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2020-11-02 21:59:37 +00:00
int S_PlayActorSound ( int 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 ) :
2021-12-21 17:19:45 +00:00
S_PlaySound3D ( soundNum , actor , & actor - > spr . pos , channel , flags ) ) ;
2020-06-11 07:22:16 +00:00
}
2020-11-02 21:59:37 +00:00
void S_StopSound ( int sndNum , DDukeActor * actor , int channel )
2020-06-11 07:22:16 +00:00
{
2020-09-11 18:17:36 +00:00
sndNum = GetReplacementSound ( sndNum ) ;
2020-11-02 21:59:37 +00:00
if ( ! actor ) soundEngine - > StopSoundID ( sndNum + 1 ) ;
2020-07-07 18:27:21 +00:00
else
{
2021-11-28 09:31:15 +00:00
if ( channel = = - 1 ) soundEngine - > StopSound ( SOURCE_Actor , actor , - 1 , sndNum + 1 ) ;
2020-11-02 21:59:37 +00:00
else soundEngine - > StopSound ( SOURCE_Actor , actor , channel , - 1 ) ;
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
}
2020-11-02 21:59:37 +00:00
void S_ChangeSoundPitch ( int soundNum , DDukeActor * actor , int pitchoffset )
2020-06-11 07:22:16 +00:00
{
2020-09-11 18:17:36 +00:00
soundNum = GetReplacementSound ( soundNum ) ;
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
{
soundEngine - > ChangeSoundPitch ( SOURCE_Unattached , nullptr , CHAN_AUTO , expitch , soundNum + 1 ) ;
}
else
{
2021-11-28 09:31:15 +00:00
soundEngine - > ChangeSoundPitch ( SOURCE_Actor , actor , CHAN_AUTO , expitch , soundNum + 1 ) ;
2020-07-07 18:27:21 +00:00
}
2020-06-11 07:22:16 +00:00
}
//==========================================================================
//
//
//
//==========================================================================
2020-11-02 21:59:37 +00:00
int S_CheckActorSoundPlaying ( DDukeActor * actor , int soundNum , int channel )
2020-06-11 07:22:16 +00:00
{
2020-09-11 18:17:36 +00:00
soundNum = GetReplacementSound ( soundNum ) ;
2020-11-02 21:59:37 +00:00
if ( actor = = nullptr ) return soundEngine - > GetSoundPlayingInfo ( SOURCE_Any , nullptr , soundNum + 1 ) ;
2021-11-28 09:31:15 +00:00
return soundEngine - > IsSourcePlayingSomething ( SOURCE_Actor , actor , channel , soundNum + 1 ) ;
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 ;
2021-11-28 09:31:15 +00:00
return soundEngine - > IsSourcePlayingSomething ( SOURCE_Actor , actor , CHAN_AUTO , 0 ) ;
2020-06-11 07:22:16 +00:00
}
int S_CheckSoundPlaying ( int soundNum )
{
2020-09-11 18:17:36 +00:00
soundNum = GetReplacementSound ( soundNum ) ;
2020-07-07 18:27:21 +00:00
return soundEngine - > GetSoundPlayingInfo ( SOURCE_Any , nullptr , soundNum + 1 ) ;
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-19 20:34:59 +00:00
static int32_t g_cdTrack = - 1 ;
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 ;
auto & s_sfx = soundEngine - > GetSounds ( ) ;
for ( unsigned i = 1 ; i < s_sfx . Size ( ) ; i + + )
{
auto fname = s_sfx [ i ] . name ;
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 )
{
s_sfx . Reserve ( 1 ) ;
s_sfx . Last ( ) = s_sfx [ i ] ;
s_sfx . Last ( ) . name = fname ;
s_sfx . Last ( ) . lumpnum = lump ;
s_sfx [ i ] . UserData [ kWorldTourMapping ] = s_sfx . Size ( ) - 1 ;
}
}
}
}
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 ( )
{
if ( currentCommentarySound > 0 )
{
soundEngine - > StopSound ( SOURCE_None , nullptr , CHAN_VOICE , currentCommentarySound ) ;
}
}
2020-10-26 06:30:34 +00:00
bool 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 ] ) ;
if ( id = = 0 )
{
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 ;
}
2021-04-22 17:59:45 +00:00
DEFINE_ACTION_FUNCTION_NATIVE ( _Duke , PlaySpecialMusic , S_PlaySpecialMusic )
{
PARAM_PROLOGUE ;
PARAM_INT ( song ) ;
S_PlaySpecialMusic ( song ) ;
return 0 ;
}
static int PlaySound ( int num , int chan , int flags , double vol )
{
return S_PlaySound ( num , chan , EChanFlags : : FromInt ( flags ) , float ( vol ) ) ;
}
2021-04-23 14:03:25 +00:00
DEFINE_ACTION_FUNCTION_NATIVE ( _Duke , PlaySound , PlaySound )
2021-04-22 17:59:45 +00:00
{
PARAM_PROLOGUE ;
PARAM_INT ( snd ) ;
PARAM_INT ( chan ) ;
PARAM_INT ( flags ) ;
PARAM_FLOAT ( vol ) ;
ACTION_RETURN_INT ( PlaySound ( snd , chan , flags , vol ) ) ;
}
2021-04-23 14:03:25 +00:00
static void StopSound ( int num )
{
S_StopSound ( num ) ;
}
DEFINE_ACTION_FUNCTION_NATIVE ( _Duke , StopSound , StopSound )
{
PARAM_PROLOGUE ;
PARAM_INT ( snd ) ;
StopSound ( snd ) ;
return 0 ;
}
2021-04-22 17:59:45 +00:00
2021-04-23 08:07:02 +00:00
DEFINE_ACTION_FUNCTION_NATIVE ( _Duke , CheckSoundPlaying , S_CheckSoundPlaying )
{
PARAM_PROLOGUE ;
PARAM_INT ( snd ) ;
ACTION_RETURN_INT ( S_CheckSoundPlaying ( snd ) ) ;
}
2020-06-11 07:22:16 +00:00
END_DUKE_NS