thug/Code/Gel/SoundFX/soundfx.cpp
2016-02-14 08:39:12 +11:00

1834 lines
56 KiB
C++

/*****************************************************************************
** **
** Neversoft Entertainment **
** **
** Copyright (C) 1999 - All Rights Reserved **
** **
******************************************************************************
** **
** Project: GEL Library **
** **
** Module: Sound effects (Sfx) **
** **
** File name: soundfx.cpp **
** **
** Created by: 01/10/01 - dc **
** **
** Description: SoundFX for the game. These are the sounds that are **
** loaded into memory. Streaming SoundFX are in the Music **
** modules (music.cpp and p_music.cpp) which should **
** actually be called the Streaming module. **
** **
*****************************************************************************/
/*****************************************************************************
** Includes **
*****************************************************************************/
#include <core/defines.h>
#include <gfx/nxviewman.h>
#include <gfx/nxloadscreen.h>
#include <gel/soundfx/soundfx.h>
#include <gel/music/music.h>
#include <gel/scripting/script.h>
#include <gel/scripting/symboltable.h>
#include <gel/scripting/checksum.h>
#include <gel/scripting/struct.h>
#include <modules/frontend/frontend.h> // until gametimer is moved into lower level stuff..
#include <sys/config/config.h>
#include <sys/replay/replay.h>
#include <gel/components/soundcomponent.h>
#include <sk/modules/skate/skate.h>
#include <sk/objects/moviecam.h>
#include <sk/objects/movingobject.h>
// Used by the script debugger code to fill in a structure
// for transmitting to the monitor.exe utility running on the PC.
void ObjectSoundInfo::GetDebugInfo(Script::CStruct *p_info)
{
#ifdef __DEBUG_CODE__
Dbg_MsgAssert(p_info,("NULL p_info sent to ObjectSoundInfo::GetDebugInfo"));
p_info->AddChecksum("checksum",checksum);
p_info->AddFloat("dropoffDist",dropoffDist);
p_info->AddFloat("currentPitch",currentPitch);
p_info->AddFloat("currentVolume",currentVolume);
p_info->AddFloat("origPitch",origPitch);
p_info->AddFloat("origVolume",origVolume);
p_info->AddFloat("targetPitch",targetPitch);
p_info->AddFloat("targetVolume",targetVolume);
p_info->AddFloat("deltaPitch",deltaPitch);
p_info->AddFloat("deltaVolume",deltaVolume);
p_info->AddInteger("timeForNextDistCheck",timeForNextDistCheck);
#endif
}
namespace Sfx
{
/******************************************************************/
/* */
/* */
/******************************************************************/
bool NoSoundPlease( void )
{
# if NO_SOUND_PLEASE
return true;
# else
// Cannot have sound if building for proview, because there is not enough IOP memory
// (ProView uses up some IOP memory with it's monitor)
if( Config::GetHardware() == Config::HARDWARE_PS2_PROVIEW )
{
return true;
}
return false;
# endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
EDropoffFunc GetDropoffFunctionFromChecksum(uint32 checksum)
{
switch (checksum)
{
case 0xef082878: // standard
return DROPOFF_FUNC_STANDARD;
case 0xf221474c: // linear
return DROPOFF_FUNC_LINEAR;
case 0x9706104b: // exponential
return DROPOFF_FUNC_EXPONENTIAL;
case 0xca64c41: // inv_exponential
return DROPOFF_FUNC_INV_EXPONENTIAL;
default:
Dbg_MsgAssert(0, ("Invalid dropoff function type %s (%x)", Script::FindChecksumName(checksum), checksum));
return DROPOFF_FUNC_STANDARD;
}
}
DefineSingletonClass( CSfxManager, "Sound FX" );
static float DefaultDropoffDist = DEFAULT_DROPOFF_DIST;
VoiceInfo VoiceInfoTable[NUM_VOICES];
// The entries will be permanent entries, followed by temporary entries...
WaveTableEntry WaveTable[PERM_WAVE_TABLE_MAX_ENTRIES + WAVE_TABLE_MAX_ENTRIES];
// This structure is used on sounds that aren't looping, but are long enough that they need to be updated for
// volume and pan (like the things pedestrians are saying to each other for example).
struct PositionalSoundEntry
{
int uniqueID;
uint32 checksum;
uint32 flags;
Obj::CSoundComponent *pObj;
struct PositionalSoundEntry *pNext;
struct PositionalSoundEntry *pPrev;
};
#define POSITIONAL_SOUND_FLAG_OCCUPIED ( 1 << 0 )
#define POSITIONAL_SOUND_FLAG_DOPPLER ( 1 << 1 )
static PositionalSoundEntry *GpPositionalSounds = NULL;
static PositionalSoundEntry PositionalSounds[MAX_POSITIONAL_SOUNDS];
int NumWavesInTable = 0;
int NumWavesInPermTable = 0;
int NumPositionalSounds = 0;
float gVolume = 100.0f;
/******************************************************************/
/* */
/* */
/******************************************************************/
sVolume::sVolume( void )
{
// Set the volume type to be the default volume type of the manager.
Sfx::CSfxManager * p_manager = Sfx::CSfxManager::Instance();
if( p_manager )
{
m_volume_type = p_manager->GetDefaultVolumeType();
}
else
{
m_volume_type = VOLUME_TYPE_BASIC_2_CHANNEL;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
sVolume::sVolume( EVolumeType type )
{
m_volume_type = type;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
sVolume::sVolume( const sVolume& rhs )
{
m_volume_type = rhs.m_volume_type;
uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
for( uint32 c = 1; c < num_channels; ++c )
{
m_channels[c] = rhs.m_channels[c];
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
sVolume::~sVolume( void )
{
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void sVolume::PercentageAdjustment( float percentage )
{
uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
for( uint32 c = 0; c < num_channels; ++c )
{
m_channels[c] = PERCENT( m_channels[c], percentage );
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
float sVolume::GetLoudestChannel( void )
{
float loudest = m_channels[0];
uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
for( uint32 c = 1; c < num_channels; ++c )
{
// Allow for phase shifting, which would make a channel negative.
if( fabsf( m_channels[c] ) > fabsf( loudest ))
loudest = m_channels[c];
}
return loudest;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool sVolume::IsSilent( void )
{
// All volume types have at least two channels.
if(( m_channels[0] != 0.0f ) || ( m_channels[1] != 0.0f ))
return false;
// Check remaining channels for 5.1 type volumes.
if( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 )
{
if(( m_channels[2] != 0.0f ) || ( m_channels[3] != 0.0f ) || ( m_channels[4] != 0.0f ))
return false;
}
return true;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void sVolume::SetSilent( void )
{
// Strictly speaking, we only need to set as many channels silent as the type
// of the volume requires, but it's probably no less efficient to do it this way.
memset( m_channels, 0, sizeof( float ) * MAX_CHANNELS );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool sVolume::operator== ( const sVolume& rhs )
{
if (m_volume_type != rhs.m_volume_type)
{
return false;
}
uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
for( uint32 c = 1; c < num_channels; ++c )
{
if (m_channels[c] != rhs.m_channels[c])
{
return false;
}
}
return true;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CSfxManager::CSfxManager( void )
{
if( NoSoundPlease())
return;
// Set the default volume type to the most basic available - platform specific code
// may override this.
SetDefaultVolumeType( VOLUME_TYPE_BASIC_2_CHANNEL );
InitSoundFX( this );
// Clear out the uniqueID table. Only has to be done once (even if module is reset).
for( int i = 0; i < NUM_VOICES; i++ )
{
VoiceInfoTable[i].uniqueID = 0;
VoiceInfoTable[i].controlID = 0;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CSfxManager::~CSfxManager( void )
{
if( NoSoundPlease())
return;
CleanUp();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::CleanUp( void )
{
// Call this at the end of a level (or the beginning of the next level before loading sounds, or both).
if( NoSoundPlease())
return;
GpPositionalSounds = NULL;
NumPositionalSounds = 0;
for( int i = 0; i < MAX_POSITIONAL_SOUNDS; i++ )
{
PositionalSounds[i].flags = 0;
}
// Should stop all sounds and do whatever cleanup necessary so soundfx can be used in the next phase
// (next level, frontend, whatever).
CleanUpSoundFX();
NumWavesInTable = 0;
DefaultDropoffDist = DEFAULT_DROPOFF_DIST;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool CSfxManager::IDAvailable(uint32 id)
{
return !SoundIsPlaying(id, NULL);
}
/******************************************************************/
/* */
/* */
/******************************************************************/
int CSfxManager::GetVoiceFromID(uint32 id)
{
int i;
// Check unique ID's first
for( i = 0; i < NUM_VOICES; i++ )
{
if ((VoiceInfoTable[i].uniqueID == id) && VoiceIsOn(i))
{
return i;
}
}
// Now the control ID's
for( i = 0; i < NUM_VOICES; i++ )
{
if ((VoiceInfoTable[i].controlID == id) && VoiceIsOn(i))
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ) )
{
Dbg_MsgAssert(0, ("Trying to control duplicate sound %s by checksum", Script::FindChecksumName(id)));
}
else
{
Dbg_Message("ERROR: Trying to control duplicate sound %s by checksum", Script::FindChecksumName(id));
}
return i;
}
}
return -1;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
uint32 CSfxManager::GenerateUniqueID(uint32 id)
{
// Keep incrementing ID until one works.
while (!IDAvailable(id))
id++;
return id;
}
/******************************************************************/
/* */
/* Different levels might want to have different dropoff */
/* distances. */
/* */
/******************************************************************/
void CSfxManager::SetDefaultDropoffDist( float dist )
{
if( NoSoundPlease())
return;
Dbg_MsgAssert( dist > 0.0f, ( "Can't set dropoff dist to 0 or less." ));
DefaultDropoffDist = dist;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::StopAllSounds( void )
{
if( NoSoundPlease())
return;
StopAllSoundFX();
GpPositionalSounds = NULL;
NumPositionalSounds = 0;
for( int i = 0; i < MAX_POSITIONAL_SOUNDS; i++ )
{
PositionalSounds[i].flags = 0;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::PauseSounds( void )
{
if( NoSoundPlease())
return;
PauseSoundsPlease();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
WaveTableEntry * CSfxManager::GetWaveTableIndex( uint32 checksum )
{
if( NoSoundPlease())
return NULL;
// Check through list of perm sounds.
for( int i = 0; i < NumWavesInPermTable; i++ )
{
if ( checksum == WaveTable[i].checksum )
return &WaveTable[i];
}
// Check through list of temp sounds.
for( int i = 0; i < NumWavesInTable; i++ )
{
if( checksum == WaveTable[i + PERM_WAVE_TABLE_MAX_ENTRIES].checksum )
return &WaveTable[i + PERM_WAVE_TABLE_MAX_ENTRIES];
}
// Not found.
return NULL;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
int CSfxManager::MemAvailable( void )
{
return GetMemAvailable();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool CSfxManager::PositionalSoundSilenceMode()
{
// Add any conditions here where you want to set the positional sound
// to 0.
// Don't update if loading screen is active
if( Nx::CLoadScreen::sIsActive() )
{
return true;
}
// Don't update if the game is paused
if( Mdl::FrontEnd::Instance()->GamePaused() )
{
return true;
}
#if 0 // TT2808: Seemed to cause more problems than it helped.
// Don't update if the game is playing a CamAnim
Mdl::Skate* skate_mod = Mdl::Skate::Instance();
if ( skate_mod->GetMovieManager()->IsRolling() )
{
return true;
}
#endif
//Dbg_Message("Updating sounds frame %d", Tmr::GetRenderFrame());
return false;
}
/******************************************************************/
/* */
/* Returns true if sound is still playing. */
/* Adjust the pitch for doppler, and the volume and pan for */
/* position. */
/* */
/******************************************************************/
bool CSfxManager::AdjustObjectSound( Obj::CSoundComponent *pObj, VoiceInfo *pVoiceInfo, Tmr::Time gameTime )
{
Dbg_Assert( pVoiceInfo );
ObjectSoundInfo *pInfo = &pVoiceInfo->info;
float dist;
// Get dropoff distance if sent to Obj_PlaySound. If not, get default (set up in LoadSound).
float dropoffDist = ( pInfo->dropoffDist == 0.0f ) ? DefaultDropoffDist : pInfo->dropoffDist;
Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pObj->GetPosition(), &dist );
if( !pCamera )
{
Dbg_Message( "Warning: Positional sound, but no camera... Sound won't be updated." );
// If there is no camera, just don't update the sound but keep it playing the same as before.
// (If we return false, the object will delete its sound).
// We might not be able to find the camera at the start of the level or perhaps when changing levels.
return true;
}
Mth::Vector dropoff_pos;
Mth::Vector *p_dropoff_pos = NULL;
if (pObj->GetClosestDropoffPos(pCamera, dropoff_pos))
{
p_dropoff_pos = &dropoff_pos;
}
// See if the object sound could be turned off (far enough away from the camera).
else if( pInfo->timeForNextDistCheck <= gameTime )
{
// Dbg_Message( "checking if we need to turn %s off", Script::FindChecksumName( pInfo->checksum ) );
if( dist >= ( dropoffDist + DIST_FROM_DROPOFF_AT_WHICH_TO_STOP_SOUND ))
{
// Dbg_Message( "sound %s auto off", Script::FindChecksumName( pInfo->checksum ) );
// If the sound is looping, let the scripts continue to think the
// sound is still around and keep pitch/volume adjusted for the action.
if( pVoiceInfo->waveIndex->platformWaveInfo.looping )
{
// Turn the sound off, monitor the sound from the object update.
pObj->SoundOutOfRange( pInfo, gameTime );
}
// Should cause this sound to be stopped and voice cleared!
return false;
}
// Still okay, sound still in range. Continue playing for x more milliseconds.
pInfo->timeForNextDistCheck = gameTime + 2000;
}
float pitch = 100.0f;
sVolume vol;
vol.SetSilent();
// If dist is beyond dropoff dist, leave volume at zero.
if( /*!PositionalSoundSilenceMode() &&*/ ( p_dropoff_pos || (dist < dropoffDist) ) )
{
Mth::Vector obj_pos(pObj->GetClosestEmitterPos(pCamera));
if( pVoiceInfo->waveIndex->flags & SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER )
{
// Even if the object didn't move, the camera could have!
AdjustPitchForDoppler( &pitch, obj_pos, pObj->GetClosestOldEmitterPos(pCamera), Tmr::FrameLength(), pCamera );
}
SetVolumeFromPos( &vol, obj_pos, dropoffDist, pInfo->dropoffFunction, pCamera, p_dropoff_pos );
}
if( !Config::CD())
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ) && !vol.IsSilent())
{
Dbg_Message( "Updating sound %s with volL = %f volR = %f", Script::FindChecksumName( pInfo->checksum ), vol.GetChannelVolume(0), vol.GetChannelVolume(1));
}
}
return UpdateLoopingSound( pVoiceInfo->uniqueID, &vol, pitch );
}
/******************************************************************/
/* */
/* Only updates one sound for frame (since it's slow to send */
/* commands between the EE and the IOP) */
/* */
/******************************************************************/
void CSfxManager::UpdatePositionalSounds( void )
{
PositionalSoundEntry *pEntry = GpPositionalSounds;
PositionalSoundEntry *pTemp;
Tmr::Time gameTime = Tmr::GetTime();
// static int active = 0; // Mick: Only update this one this frame.
// int count = 0; // Counter for all the sounds.
// uint32 start_time = Tmr::GetTimeInUSeconds();
while ( pEntry )
{
pTemp = pEntry;
pEntry = pEntry->pNext;
Dbg_MsgAssert( pTemp->flags & POSITIONAL_SOUND_FLAG_OCCUPIED, ( "Sounds in the list should have 'occupied' turned on." ));
// This just filters the current volume/pitch towards the target, and should be done every frame per sound:
Dbg_MsgAssert( pTemp->uniqueID,( "UniqueID 0 means no sound was played. Shouldn't have gotten this far." ));
int voiceIndex;
if( !SoundIsPlaying( pTemp->uniqueID, &voiceIndex ))
{
// Wasn't a looping sound, and it's done playing.
RemovePositionalSoundFromList( pTemp, false );
}
else
{
VoiceInfo *pVoiceInfo = &VoiceInfoTable[voiceIndex];
pTemp->pObj->UpdateObjectSound( &pVoiceInfo->info );
// Only do the low-down nitty gritty positional stuff on one sound per frame.
//if(active == count)
{
Dbg_MsgAssert( pTemp->pObj,( "Null object pointer in positional sound." ));
# ifdef __USE_PROFILER__
//Sys::CPUProfiler->PushContext( 0, 0, 128 ); // blue = this bit of sound code.
# endif // __USE_PROFILER__
if( !AdjustObjectSound( pTemp->pObj, pVoiceInfo, gameTime ))
{
RemovePositionalSoundFromList( pTemp, true );
}
# ifdef __USE_PROFILER__
//Sys::CPUProfiler->PopContext();
# endif // __USE_PROFILER__
}
}
//count++;
}
// uint32 end_time = Tmr::GetTimeInUSeconds();
// if ((end_time - start_time) > 250)
// Dbg_Message("CSfxManager::UpdatePositionalSounds Frame %d Time %d (%x - %x)", Tmr::GetVblanks(), (end_time - start_time), end_time, start_time);
#if 0 // We can update all sounds now
// Increment the active sound and wrap based on current count.
active++;
// Wrap based on current count.
if( active >= count )
{
active = 0;
}
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::RemovePositionalSoundFromList( PositionalSoundEntry *pEntry, bool stopIfPlaying )
{
pEntry->flags = 0;
// Stop the sound from playing if it is playing.
if( stopIfPlaying && SoundIsPlaying( pEntry->uniqueID ))
{
StopSound( pEntry->uniqueID );
}
if( pEntry->pNext )
{
pEntry->pNext->pPrev = pEntry->pPrev;
}
if ( pEntry->pPrev )
{
pEntry->pPrev->pNext = pEntry->pNext;
}
else
{
GpPositionalSounds = pEntry->pNext;
}
NumPositionalSounds--;
}
/******************************************************************/
/* */
/* Called from the object's destructor. */
/* Stops all soundfx and streams associated with the object. */
/* */
/******************************************************************/
void CSfxManager::ObjectBeingRemoved( Obj::CSoundComponent *pObj )
{
PositionalSoundEntry *pEntry = GpPositionalSounds;
PositionalSoundEntry *pTemp;
while( pEntry )
{
pTemp = pEntry;
pEntry = pEntry->pNext;
if( pTemp->pObj == pObj )
{
RemovePositionalSoundFromList( pTemp, true );
// Keep scanning the list, in case there's more than one...
}
}
}
/******************************************************************/
/* */
/* Stop a sound on an object. */
/* */
/******************************************************************/
void CSfxManager::StopObjectSound( Obj::CSoundComponent *pObj, uint32 checksum )
{
PositionalSoundEntry *pEntry = GpPositionalSounds;
PositionalSoundEntry *pTemp;
while( pEntry )
{
pTemp = pEntry;
pEntry = pEntry->pNext;
if( pTemp->pObj == pObj )
{
if(( !checksum ) || ( pTemp->checksum == checksum ))
{
// This will stop the sound as well as removing it from the list.
RemovePositionalSoundFromList( pTemp, true );
// Keep scanning the list, in case there's more than one.
}
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::AddPositionalSoundToUpdateList( uint32 uniqueID, uint32 soundChecksum, Obj::CSoundComponent *pObj )
{
if( !uniqueID )
{
// Sound play was unsuccessful.
return;
}
if( NumPositionalSounds == MAX_POSITIONAL_SOUNDS )
{
Dbg_Message( "WARNING: Ran out of positional sound slots.\nIncrease the number of slots, or decrease the number of requests." );
return;
}
NumPositionalSounds++;
int i;
for( i = 0; i < MAX_POSITIONAL_SOUNDS; i++ )
{
PositionalSoundEntry *pEntry = &PositionalSounds[i];
if( !( pEntry->flags & POSITIONAL_SOUND_FLAG_OCCUPIED ))
{
if( GpPositionalSounds )
{
GpPositionalSounds->pPrev = pEntry;
}
pEntry->pNext = GpPositionalSounds;
pEntry->pPrev = NULL;
GpPositionalSounds = pEntry;
pEntry->uniqueID = uniqueID;
pEntry->checksum = soundChecksum;
pEntry->flags = POSITIONAL_SOUND_FLAG_OCCUPIED;
pEntry->pObj = pObj;
return;
}
}
Dbg_MsgAssert( 0, ( "This is impossible." ));
}
/******************************************************************/
/* */
/* SFX Name should be the name of the sound, no extension or */
/* path. It will be the responsibility of the loader for each */
/* platform to add the path and append the extension. */
/* This module assumes the sounds are all loaded at the beginning */
/* of a level or module, and all removed at the end. */
/* */
/******************************************************************/
bool CSfxManager::LoadSound( const char *sfxName, int flags, float dropoff, float pitch, float volume )
{
if( NoSoundPlease())
return false;
const char *pNameMinusPath = sfxName;
int stringLength = strlen( sfxName );
for( int i = 0; i < stringLength; i++ )
{
if(( sfxName[i] == '\\' ) || ( sfxName[i] == '/' ))
pNameMinusPath = &sfxName[i + 1];
}
uint32 checksum = Script::GenerateCRC( pNameMinusPath );
WaveTableEntry *pWaveEntry = NULL;
// See if the sound is already loaded.
if( NULL != GetWaveTableIndex( checksum ))
{
if( !Config::CD())
{
if( Script::GetInt( 0xd7bb618d /* DebugSoundFx */, false ))
{
Dbg_Message( "Warning: Sound '%s' already loaded (maybe from a different directory)?", sfxName );
}
}
return false;
}
if( flags & SFX_FLAG_LOAD_PERM )
{
if( NumWavesInPermTable >= PERM_WAVE_TABLE_MAX_ENTRIES )
{
Dbg_Message( "Increase PERM_WAVE_TABLE_MAX_ENTRIES, no room for %s", sfxName );
Dbg_MsgAssert( 0,( "Too many permanent sound waves being loaded." ));
return false;
}
pWaveEntry = &WaveTable[ NumWavesInPermTable ];
if( !LoadSoundPlease( sfxName, checksum, &pWaveEntry->platformWaveInfo, 1 ))
{
Dbg_Message( "failed to load sound %s", sfxName );
return false;
}
NumWavesInPermTable++;
}
else
{
if( NumWavesInTable >= WAVE_TABLE_MAX_ENTRIES )
{
Dbg_MsgAssert( 0, ( "Too many sound waves being loaded." ));
return false;
}
pWaveEntry = &WaveTable[ NumWavesInTable + PERM_WAVE_TABLE_MAX_ENTRIES ];
if( !LoadSoundPlease( sfxName, checksum, &pWaveEntry->platformWaveInfo ))
{
return false;
}
NumWavesInTable++;
}
Dbg_MsgAssert( volume >= 0.0f,( "Tweak volume less than zero." ));
Dbg_MsgAssert( volume <= MAX_VOL_ALLOWED,( "Tweak volume greater than max allowed." ));
Dbg_Assert( pWaveEntry );
pWaveEntry->dropoff = dropoff;
pWaveEntry->pitch = pitch;
pWaveEntry->volume = volume;
pWaveEntry->checksum = checksum;
pWaveEntry->flags = flags;
return true;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::TweakVolumeAndPitch( sVolume *p_vol, float *pitch, WaveTableEntry* waveTableIndex )
{
if( NoSoundPlease())
return;
Dbg_Assert( p_vol && pitch );
// 100% volume would result in 1/2 the real MAX. This gives headroom for louder sounds!
p_vol->PercentageAdjustment( 50.0f );
// Apply tweaked volumes, pitches to sound (variables from LoadSound).
if( waveTableIndex->volume != 100.0f )
{
p_vol->PercentageAdjustment( waveTableIndex->volume );
}
if( waveTableIndex->pitch != 100.0f )
{
*pitch = PERCENT( *pitch, waveTableIndex->pitch );
}
}
/******************************************************************/
/* */
/* Returns a unique ID each time a sound is played, 0 on failure. */
/* The 'unique ID' is just a number that is incremented each */
/* time a sound is played. */
/* */
/* For each platform, keep a table that stores these unique ID's */
/* for each voice. PS2 has 48 voices. */
/* */
/* If somebody calls "StopSound" I check each of the entries in */
/* the table for a match, and if a match is found and that voice */
/* is playing, I stop the voice. */
/* */
/* If a match is not found, that means the voice stopped playing */
/* earlier and somebody else may be using the voice now, so that */
/* voice shouldn't be stopped */
/* */
/******************************************************************/
uint32 CSfxManager::PlaySound( uint32 checksum, sVolume *p_vol, float pitch, uint32 controlID, SoundUpdateInfo *pUpdateInfo, const char *pSoundName )
{
if( NoSoundPlease())
return 0;
Dbg_Assert( p_vol );
WaveTableEntry *waveTableIndex = GetWaveTableIndex( checksum );
if( waveTableIndex == NULL )
{
//Dbg_MsgAssert( 0,( "Couldn't find sound %s", Script::FindChecksumName( checksum )));
Dbg_Message("******** NON-FATAL ERROR: Couldn't find sound %s", Script::FindChecksumName( checksum ));
return 0;
}
// Just use checksum if no controlID is set
if (controlID == 0)
{
controlID = checksum;
}
if( !Config::CD())
{
if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
{
if (!pUpdateInfo && !IDAvailable(controlID))
{
Dbg_Message("Playing sound %s with same checksum as one already being played, won't be able to control from script directly.", Script::FindChecksumName(controlID));
}
}
}
TweakVolumeAndPitch( p_vol, &pitch, waveTableIndex );
// Continue to play the sound, even if the volume is zero. We might be starting a looping sound,
// so don't second guess this.
// For the moment, don't record looping sounds, due to problems with running out of voices due to
// voices still being used by game when it is paused. They just have their volume turned down to zero.
if( !waveTableIndex->platformWaveInfo.looping )
{
// Only record the sound in the replay if the game is not paused, otherwise the menu sounds in the
// paused menu will get recorded.
Mdl::FrontEnd* p_frontend = Mdl::FrontEnd::Instance();
if( !p_frontend->GamePaused())
{
// Replay::WritePlaySound( checksum, volL, volR, pitch );
}
}
// In PlaySoundPlease(), clip sounds to the max allowable. If no volume modifiers are listed in the script
// commands (LoadSound or PlaySound) and the sound is played without attenuation, volL and volR will be 1/2
// the max (so 50 at this point). Find the platform specific volumes like this:
// leftVolume = PERCENT( volL, PLATFORM_MAX ). Clip to PLATFORM_MAX (or -PLATFORM_MAX if phasing is supported).
#ifdef __PLAT_NGPS__
int whichVoice = PlaySoundPlease( &waveTableIndex->platformWaveInfo, p_vol, pitch, waveTableIndex->flags & SFX_FLAG_NO_REVERB);
#else
int whichVoice = PlaySoundPlease( &waveTableIndex->platformWaveInfo, p_vol, pitch );
#endif
if( whichVoice == -1 )
{
Dbg_Message( "Couldn't find free voice." );
return 0;
}
Dbg_MsgAssert( whichVoice < NUM_VOICES,( "Voice larger than max allowed." ));
VoiceInfo *pVoiceInfo = &VoiceInfoTable[whichVoice];
pVoiceInfo->controlID = controlID;
pVoiceInfo->uniqueID = GenerateUniqueID(controlID);
// So we don't have to look this up every time we query this module for the properties of a sound that's
// playing (which we do often for looping sounds).
pVoiceInfo->waveIndex = waveTableIndex;
ObjectSoundInfo *pInfo = &pVoiceInfo->info;
if ( pUpdateInfo )
{
pInfo->currentPitch = pUpdateInfo->pitch;
pInfo->currentVolume = pUpdateInfo->volume;
pInfo->dropoffDist = pUpdateInfo->dropoffDist ? pUpdateInfo->dropoffDist : waveTableIndex->dropoff;
pInfo->origPitch = pInfo->targetPitch = pInfo->currentPitch;
pInfo->origVolume = pInfo->targetVolume = pInfo->currentVolume;
pInfo->deltaPitch = 0.0f;
pInfo->deltaVolume = 0.0f;
pInfo->dropoffFunction = pUpdateInfo->dropoffFunction;
}
else
{
pInfo->currentPitch = 100.0f;
pInfo->currentVolume = 100.0f;
pInfo->dropoffDist = 0.0f;
pInfo->dropoffFunction = DROPOFF_FUNC_STANDARD;
}
pInfo->checksum = checksum;
if( !Config::CD())
{
if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
{
//Dbg_Message( "Playing sound %s\n", (pSoundName) ? pSoundName : Script::FindChecksumName( checksum ));
Dbg_Message( "Playing sound %s with volL = %f volR = %f on voice %d", (pSoundName) ? pSoundName : Script::FindChecksumName( checksum ),
p_vol->GetChannelVolume(0), p_vol->GetChannelVolume(1), whichVoice);
}
}
return pVoiceInfo->uniqueID;
}
/******************************************************************/
/* */
/* Send in the uniqueID returned from PlaySound() */
/* */
/******************************************************************/
bool CSfxManager::StopSound( uint32 uniqueID )
{
if( NoSoundPlease())
return true;
int whichVoice = GetVoiceFromID(uniqueID);
if (whichVoice >= 0)
{
// Stop the sound if it's playing (platform specific).
StopSoundPlease( whichVoice );
return true;
}
return false;
}
bool CSfxManager::SetSoundParams( uint32 uniqueID, sVolume *p_vol, float pitch )
{
if( NoSoundPlease())
return true;
int whichVoice = GetVoiceFromID(uniqueID);
if (whichVoice >= 0)
{
WaveTableEntry *waveTableIndex = VoiceInfoTable[ whichVoice ].waveIndex;
if( waveTableIndex == NULL )
{
Dbg_MsgAssert(0, ( "Can't find wave table entry for sound %s", Script::FindChecksumName( uniqueID ) ) );
return false;
}
if( !p_vol->IsSilent())
{
// Tweak the values to designer specs (from LoadSound and PlaySound).
TweakVolumeAndPitch( p_vol, &pitch, waveTableIndex );
}
# if defined( __PLAT_NGPS__ ) || defined( __PLAT_NGC__ )
// Adjust pitch to account for lower sampling rates.
pitch = PERCENT( pitch, waveTableIndex->platformWaveInfo.pitchAdjustmentForSamplingRate );
# endif
// And finally, set the parameters.
SetVoiceParameters( whichVoice, p_vol, pitch );
return true;
}
if( !Config::CD())
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
{
Dbg_Message( "SetSoundParams: couldn't find sound %s", Script::FindChecksumName( uniqueID ));
}
}
return false;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::SetMainVolume( float volume )
{
if( NoSoundPlease())
return;
Dbg_MsgAssert( volume >= 0.0f, ( "Volume below 0%" ));
Dbg_MsgAssert( volume <= 100.0f, ( "Volume above 100%" ));
if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
{
Dbg_Message( "Setting gVolume to %f\n",volume );
}
gVolume = volume;
volume = volume * Config::GetMasterVolume() / 100.0f;
if( volume < 0.1f )
{
volume = 0.0f;
}
SetVolumePlease( volume );
Pcm::SetAmbientVolume( volume );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
float CSfxManager::GetMainVolume( void )
{
return gVolume;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::SetReverb( float reverbLevel, int reverbMode, bool instant )
{
if( NoSoundPlease())
return;
Dbg_MsgAssert( reverbLevel >= 0.0f, ( "Reverb below 0%" ));
Dbg_MsgAssert( reverbLevel <= 100.0f, ( "Reverb above 100%" ));
SetReverbPlease( reverbLevel, reverbMode, instant );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool CSfxManager::SoundIsPlaying( uint32 uniqueID, int *pWhichVoice )
{
if( NoSoundPlease())
return 0;
int whichVoice;
// Zero is not a valid ID... quickly return.
if ( !uniqueID )
return false;
for( whichVoice = 0; whichVoice < NUM_VOICES; whichVoice++ )
{
if ( VoiceInfoTable[ whichVoice ].uniqueID == uniqueID )
{
if( !VoiceIsOn( whichVoice ))
{
return false;
}
if( pWhichVoice )
{
*pWhichVoice = whichVoice;
}
return true;
}
}
return false;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
int CSfxManager::GetNumSoundsPlaying()
{
// This is not the most efficient way to figure this out, but it doesn't
// require any changes below the p-line. We should add p-line code if
// it get used a lot.
int voicesUsed = 0;
for( int whichVoice = 0; whichVoice < NUM_VOICES; whichVoice++ )
{
if( VoiceIsOn( whichVoice ) )
{
voicesUsed++;
}
}
return voicesUsed;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
float CSfxManager::GetDropoffDist( uint32 soundChecksum )
{
if( NoSoundPlease())
return DefaultDropoffDist;
WaveTableEntry *waveIndex = GetWaveTableIndex( soundChecksum );
if( waveIndex == NULL )
{
return 0;
}
float dropoffDist = waveIndex->dropoff;
return ( dropoffDist ? dropoffDist : DefaultDropoffDist );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool CSfxManager::UpdateLoopingSound( uint32 soundID, sVolume *p_vol, float pitch )
{
if( NoSoundPlease())
return true;
// uint32 start_time = Tmr::GetTimeInUSeconds();
int whichVoice;
for( whichVoice = 0; whichVoice < NUM_VOICES; whichVoice++ )
{
if( VoiceInfoTable[whichVoice].uniqueID == soundID )
{
if( !VoiceIsOn( whichVoice ))
{
if( !Config::CD())
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
{
Dbg_Message( "Sound %s is done", Script::FindChecksumName( VoiceInfoTable[whichVoice].info.checksum ));
}
}
return false;
}
// Tweak sounds for this particular instance of PlaySound (this allows a designer to vary the pitch,
// for example, of a carloop sound on each car, by adding a pitch = parameter to Obj_PlaySound. This
// will be maintained, as will the volume, thanks to this code.
if( !p_vol->IsSilent())
{
float tweakVolume = VoiceInfoTable[whichVoice].info.currentVolume;
if( tweakVolume != 100.0f )
{
p_vol->PercentageAdjustment( tweakVolume );
}
float tweakPitch = VoiceInfoTable[ whichVoice ].info.currentPitch;
if( tweakPitch != 100.0f )
{
pitch = PERCENT( pitch, tweakPitch );
}
}
WaveTableEntry *waveTableIndex = VoiceInfoTable[ whichVoice ].waveIndex;
if( waveTableIndex == NULL )
{
Dbg_Message( "WARNING: Positional/looping sound error." );
return false;
}
if( !p_vol->IsSilent())
{
// Tweak the values to designer specs (from LoadSound and PlaySound).
TweakVolumeAndPitch( p_vol, &pitch, waveTableIndex );
}
# if defined( __PLAT_NGPS__ ) || defined( __PLAT_NGC__ )
// Adjust pitch to account for lower sampling rates.
pitch = PERCENT( pitch, waveTableIndex->platformWaveInfo.pitchAdjustmentForSamplingRate );
# endif
// And finally, set the parameters.
SetVoiceParameters( whichVoice, p_vol, pitch );
// uint32 end_time = Tmr::GetTimeInUSeconds();
// if ((end_time - start_time) > 250)
// Dbg_Message("CSfxManager::UpdateLoopingSound Frame %d Time %d", Tmr::GetVblanks(), (end_time - start_time));
return true;
}
}
if( !Config::CD())
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
{
Dbg_Message( "UpdateSound: couldn't find sound %s", Script::FindChecksumName( VoiceInfoTable[whichVoice].info.checksum ));
}
}
return false;
}
/******************************************************************/
/* */
/* Calculates multipliers for front left, front right, rear left, */
/* rear right and center channel speakers. */
/* */
/******************************************************************/
void CSfxManager::Get5ChannelMultipliers( const Mth::Vector &sound_source, float *p_multipliers )
{
float angle = Mth::RadToDeg( atan2f( sound_source[X], -sound_source[Z] ));
Get5ChannelMultipliers( angle, p_multipliers );
}
/******************************************************************/
/* */
/* Calculates multipliers for front left, front right, rear left, */
/* rear right and center channel speakers. */
/* */
/******************************************************************/
void CSfxManager::Get5ChannelMultipliers( float angle, float *p_multipliers )
{
static float speakers[5][3] = { { 330.00f, 240.01f, 359.99f }, // Front left max angle, min angle0, min angle1
{ 30.00f, 0.01f, 119.99f }, // Front right
{ 240.00f, 120.01f, 329.99f }, // Rear left
{ 120.0f, 30.01f, 239.99f }, // Rear right
{ 0.0f, 330.01f, 29.99f }}; // Center
// Ensure angle is in a suitable range.
angle = ( angle < 0.0f ) ? ( 360.0f + angle ) : angle;
// Go through and calculate the relative volumes for each speaker.
for( int spkr = 0; spkr < 5; ++spkr )
{
float amin0 = speakers[spkr][1];
float amin1 = speakers[spkr][2];
float amax = speakers[spkr][0];
float mul = 0.0f;
if( amin0 < amax )
{
// Regular test.
if(( angle > amin0 ) && ( angle <= amax ))
{
// Angle lies between amin0 and amax.
mul = ( angle - amin0 ) / ( amax - amin0 );
}
else if(( angle > amax ) && ( angle < amin1 ))
{
// Angle lies between amax and amin1.
mul = 1.0f - (( angle - amax ) / ( amin1 - amax ));
}
}
else
{
// Non regular test (center channel). Assumes center at 0.0.
if( angle > amin0 )
{
mul = ( angle - amin0 ) / ( 360.0f - amin0 );
}
else if( angle < amin1 )
{
mul = ( amin1 - angle ) / ( amin1 );
}
}
// Angle is within scope of this speaker. Figure multiplier.
Dbg_Assert( mul <= 1.0f );
p_multipliers[spkr] = sinf( mul * Mth::PI * 0.5f );
}
}
/******************************************************************/
/* */
/* Sets volume and pan considering all cameras and current */
/* position. */
/* */
/******************************************************************/
void CSfxManager::SetVolumeFromPos( sVolume *p_vol, const Mth::Vector &soundSource, float dropoffDist, EDropoffFunc dropoff_func,
Gfx::Camera *p_camera, const Mth::Vector *p_dropoff_pos)
{
if( NoSoundPlease())
return;
Dbg_Assert( p_vol );
// Set the volume to 0
p_vol->SetSilent();
// Return if in Silence Mode
if( PositionalSoundSilenceMode() )
return;
// Find the camera if one wasn't already supplied
if (!p_camera)
{
p_camera = Nx::CViewportManager::sGetClosestCamera(soundSource);
// If we still can't find a camera, return with the volume set to 0
if (!p_camera)
{
return;
}
}
// Find the dist to the sound
float dist = Mth::Distance( p_camera->GetPos(), soundSource );
// Calculate the dropoff dist
if (p_dropoff_pos)
{
dropoffDist = dist + Mth::Distance( *p_dropoff_pos, p_camera->GetPos());
}
else if( !dropoffDist )
{
dropoffDist = DefaultDropoffDist;
}
// If we are outside the dropoff dist, we are out of here
if( dist >= dropoffDist )
return;
// Sound is within range of this camera.
float dropOff = dist / dropoffDist;
float volume = 0.0f;
switch (dropoff_func)
{
case DROPOFF_FUNC_STANDARD:
volume = ( 100.0f *
((( 1.0f - ( dropOff * dropOff )) + // Exponential and...
( 3.0f * ( 1.0f - ( dropOff )))) / 4.0f )); // ...linear averaged together.
break;
case DROPOFF_FUNC_LINEAR:
volume = ( 100.0f * ( 1.0f - dropOff ) );
break;
case DROPOFF_FUNC_EXPONENTIAL:
volume = ( 100.0f * ( ( 1.0f - dropOff ) * ( 1.0f - dropOff ) ) );
break;
case DROPOFF_FUNC_INV_EXPONENTIAL:
volume = ( 100.0f * ( sqrtf ( 1.0f - dropOff ) ) );
break;
}
switch( p_vol->GetVolumeType() )
{
case VOLUME_TYPE_BASIC_2_CHANNEL:
case VOLUME_TYPE_2_CHANNEL_DOLBYII:
{
Dbg_Assert( p_camera ); // We should have returned by now
if( fabsf( volume ) > fabsf( p_vol->GetLoudestChannel()))
{
Mth::Vector sound_pos_from_camera = soundSource - p_camera->GetPos();
sound_pos_from_camera.Normalize();
Mth::Vector camRightVector = -p_camera->GetMatrix()[X];
// Project the obj_pos vector onto the right vector.
// For some reason right and left were switched (must be left instead of right!)
float panVal = Mth::DotProduct( sound_pos_from_camera, camRightVector );
// +1.0f is all the way on the right, -1.0f is all the way on the left.
// Doing an exponential curve, so that if the object is right in the middle
// the right and left volume (at closest dist) is 75%. 100% volume if all
// the way to the right/left.
panVal += 1.0f; // Will now be from 0 to 2 (Lmax to Rmax)..
panVal /= 2.0f; // ...now from 0 to 1.
float rVol = ( 1.0f - ( panVal * panVal )) * volume;
panVal = 1.0f - panVal;
float lVol = ( 1.0f - ( panVal * panVal )) * volume;
if( p_vol->GetVolumeType() == VOLUME_TYPE_2_CHANNEL_DOLBYII )
{
bool bSetPhase = false;
if( lVol > p_vol->GetChannelVolume( 0 ))
{
p_vol->SetChannelVolume( 0, lVol );
bSetPhase = true;
}
if( rVol > p_vol->GetChannelVolume( 1 ))
{
p_vol->SetChannelVolume( 1, rVol );
bSetPhase = true;
}
if( bSetPhase )
{
// If sound is behind the camera, set volume negative and it will sound out of phase.
Mth::Vector camAtVector = -p_camera->GetMatrix()[Z];
float behindCamera = Mth::DotProduct( sound_pos_from_camera, camAtVector );
if ( behindCamera < 0.0f )
{
// Just one channel needs to be reverse phased to get the effect.
p_vol->SetChannelVolume( 0, p_vol->GetChannelVolume( 0 ) * -1.0f );
}
}
}
else
{
if( lVol > p_vol->GetChannelVolume( 0 ))
{
p_vol->SetChannelVolume( 0, lVol );
}
if( rVol > p_vol->GetChannelVolume( 1 ))
{
p_vol->SetChannelVolume( 1, rVol );
}
}
}
}
break;
case VOLUME_TYPE_5_CHANNEL_DOLBY5_1:
{
Dbg_Assert( p_camera ); // We should have returned by now
if( fabsf( volume ) > p_vol->GetLoudestChannel())
{
// Transform the sound source into the camera's coordinate space.
Mth::Matrix inv_view = p_camera->GetMatrix();
inv_view.Invert();
Mth::Vector sound_src = soundSource - p_camera->GetPos();
Mth::Vector sound_pos = inv_view.Transform( sound_src );
sound_pos.Normalize();
float channel_multipliers[5];
Get5ChannelMultipliers( sound_pos, &channel_multipliers[0] );
// Go through and calculate the relative volumes for each speaker.
for( int spkr = 0; spkr < 5; ++spkr )
{
float mul = channel_multipliers[spkr];
if( mul > 0.0f )
{
// Angle is within scope of this speaker. Figure multiplier.
Dbg_Assert( mul <= 1.0f );
float vol = volume * mul;
if( vol > p_vol->GetChannelVolume( spkr ))
{
p_vol->SetChannelVolume( spkr, vol );
}
}
}
// Final step is to normalize the channels out and reset to volume level.
if( !p_vol->IsSilent())
{
float norm = 0.0;
for( int spkr = 0; spkr < 5; ++spkr )
{
norm += p_vol->GetChannelVolume( spkr ) * p_vol->GetChannelVolume( spkr );
}
norm = sqrtf( norm );
for( int spkr = 0; spkr < 5; ++spkr )
{
p_vol->SetChannelVolume( spkr, ( volume * p_vol->GetChannelVolume( spkr )) / norm );
}
}
}
}
break;
default:
break;
}
}
/* Real speed of sound ~= 760 mph. */
/* Lower that for dramatic effect. */
#define SPEED_OF_OUR_SOUND MPH_TO_INCHES_PER_SECOND( 700.0f )
/******************************************************************/
/* */
/* This should happen after all other pitch adjustments. */
/* */
/******************************************************************/
void CSfxManager::AdjustPitchForDoppler( float *pitch, const Mth::Vector &currentPos, const Mth::Vector &oldPos, float elapsedTime, Gfx::Camera *pCam )
{
# ifndef __PLAT_NGC__
const float cutoff_dist = 360.0f; // in inches
# endif // __PLAT_NGC__
if( NoSoundPlease())
return;
# ifndef __PLAT_NGC__
if ( !pCam )
{
pCam = Nx::CViewportManager::sGetClosestCamera( currentPos );
if( !pCam )
return;
}
float prevDist = Mth::Distance( pCam->m_old_pos, oldPos );
float deltaDist = Mth::Distance( pCam->GetPos(), currentPos ) - prevDist;
if(( fabsf( deltaDist ) * Tmr::FrameRatio()) > Tmr::FrameRatio() * cutoff_dist )
{
// TT2794: Large movements in the camera are causing pitch glitches, so
// it would be better to not change the pitch at all than to try to find
// a "good" change. Garrett
return;
// Clip so there aren't high pitched schreeches when camera is moved in net games and such:
if ( deltaDist < 0.0f )
{
deltaDist = -( Tmr::FrameRatio( ) * cutoff_dist );
}
else
{
deltaDist = Tmr::FrameRatio( ) * cutoff_dist;
}
}
float deltaPitch = SPEED_OF_OUR_SOUND * elapsedTime;
Dbg_MsgAssert( deltaPitch,( "Divide by zero." ));
deltaPitch = (( *pitch ) * deltaDist ) / deltaPitch;
*pitch -= deltaPitch;
# endif // __PLAT_NGC__
}
/******************************************************************/
/* */
/* This should happen after all other pitch adjustments. */
/* */
/******************************************************************/
ObjectSoundInfo *CSfxManager::GetObjectSoundProperties( Obj::CSoundComponent *pObj, uint32 checksum )
{
PositionalSoundEntry *pEntry = GpPositionalSounds;
while( pEntry )
{
if(( pEntry->pObj == pObj ) && ( pEntry->checksum == checksum ))
{
// Change the current volume, pitch.
int voiceIndex;
if( !SoundIsPlaying( pEntry->uniqueID, &voiceIndex ))
{
return NULL;
}
return &VoiceInfoTable[ voiceIndex ].info;
}
pEntry = pEntry->pNext;
}
return NULL;
}
/******************************************************************/
/* */
/* Plays sound, considering camera position(s) from sound source. */
/* */
/******************************************************************/
uint32 CSfxManager::PlaySoundWithPos( uint32 soundChecksum, SoundUpdateInfo *pUpdateInfo, Obj::CSoundComponent *pObj, bool noPosUpdate )
{
if( NoSoundPlease())
return 0;
WaveTableEntry *waveTableIndex = GetWaveTableIndex( soundChecksum );
if( waveTableIndex == NULL )
{
Dbg_MsgAssert( 0, ( "Asking to play sound that hasn't been loaded %s.", Script::FindChecksumName( soundChecksum )));
return 0;
}
Dbg_MsgAssert( pObj,( "pObj should be non-NULL" ));
sVolume vol;
// Garrett: Shouldn't we be calling this here, too? Or is it not necessary?
// if( pVoiceInfo->waveIndex->flags & SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER )
// {
// // Even if the object didn't move, the camera could have!
// AdjustPitchForDoppler( &pitch, pObj->m_pos, pObj->m_old_pos, Tmr::FrameLength(), pCamera );
// }
Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pObj->GetPosition() );
if( pCamera )
{
Mth::Vector dropoff_pos;
Mth::Vector *p_dropoff_pos = NULL;
if (pObj->GetClosestDropoffPos(pCamera, dropoff_pos))
{
p_dropoff_pos = &dropoff_pos;
}
SetVolumeFromPos( &vol, pObj->GetClosestEmitterPos(pCamera), pUpdateInfo->dropoffDist ? pUpdateInfo->dropoffDist : waveTableIndex->dropoff,
pUpdateInfo->dropoffFunction, pCamera, p_dropoff_pos );
}
else
{
vol.SetSilent();
}
Dbg_MsgAssert(!(noPosUpdate && ( waveTableIndex->flags & SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER )), ("Trying to play doppler sound with NoPosUpdate"));
if( vol.IsSilent() && noPosUpdate )
{
return 0;
}
vol.PercentageAdjustment( pUpdateInfo->volume );
uint32 uniqueID = PlaySound( soundChecksum, &vol, pUpdateInfo->pitch, 0, pUpdateInfo );
if( !Config::CD())
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
{
Dbg_Message( "Starting sound %s with volL = %f volR = %f", Script::FindChecksumName( soundChecksum ), vol.GetChannelVolume(0), vol.GetChannelVolume(1));
}
}
// Should be able to have looping sounds, as long as the ones that are manually updated call PlaySound
// instead of PlaySoundWithPos.
if(!noPosUpdate)
{
AddPositionalSoundToUpdateList( uniqueID, soundChecksum, pObj );
if( !Config::CD())
{
if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
{
Dbg_Message( "Added sound %s to update list", Script::FindChecksumName( soundChecksum ));
}
}
}
return uniqueID;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CSfxManager::Update( void )
{
PerFrameUpdate();
}
} // namespace Sfx