mirror of
https://github.com/thug1src/thug.git
synced 2024-11-30 12:06:44 +00:00
2540 lines
74 KiB
C++
2540 lines
74 KiB
C++
/*
|
|
Music.cpp -- highest level
|
|
p_music.cpp -- platform specific
|
|
pcm*.c -- the code that runs on the IOP.
|
|
|
|
This module should actually be renamed now, as it isn't just for
|
|
music any more!
|
|
|
|
This module should be called Streaming.cpp.
|
|
|
|
This module handles streaming music, and soundfx.
|
|
|
|
The stream data is read into IOP memory from the DVD, into a
|
|
double buffer in IOP memory, about once every three seconds.
|
|
|
|
The IOP chip has a thread that is running on a timer interrupt (see the pcm
|
|
code, which compiles as an IOP module EzPcm.irx). 60 times per second,
|
|
this thread updates small areas of memory on the SPU2 chip -- double
|
|
buffers for each stream that need updating approximately every second
|
|
and a half (or longer if the pitch has been turned down lower).
|
|
|
|
These voices work just like the other voices for soundfx, except that they
|
|
loop over a small area that is updated with the sound data streaming off
|
|
the DVD. You can change the pitch and volume of these voices on the fly
|
|
just like soundFX, so streaming sounds can be adjusted positionally over time.
|
|
|
|
Oh, yeah, just one more thing: yo mamma, yo pappa, yo greasy bald gradma!
|
|
*/
|
|
|
|
#include <core/defines.h>
|
|
#include <core/macros.h>
|
|
#include <core/singleton.h>
|
|
#include <core/math.h>
|
|
|
|
#include <sys/timer.h>
|
|
#include <sys/config/config.h>
|
|
|
|
#include <gel/scripting/script.h>
|
|
#include <gel/scripting/symboltable.h>
|
|
#include <gel/scripting/checksum.h>
|
|
|
|
#include <gel/soundfx/soundfx.h>
|
|
#include <gel/music/music.h>
|
|
#include <gel/components/streamcomponent.h>
|
|
|
|
#include <sk/modules/skate/skate.h>
|
|
#include <sk/modules/skate/gamemode.h>
|
|
|
|
#include <gfx/debuggfx.h>
|
|
|
|
#ifdef __PLAT_NGPS__
|
|
#include <gel/music/ngps/p_music.h>
|
|
#include <gel/music/ngps/pcm/pcm.h>
|
|
#elif defined( __PLAT_XBOX__ )
|
|
#include <gel/music/xbox/p_music.h>
|
|
#include <gel/music/xbox/p_soundtrack.h>
|
|
#elif defined( __PLAT_NGC__ )
|
|
#include <gel/music/ngc/p_music.h>
|
|
#include <gel/music/ngc/pcm/pcm.h>
|
|
#endif
|
|
#include <sys/config/config.h>
|
|
#include <sys/replay/replay.h>
|
|
#include <sys/file/asyncfilesys.h>
|
|
|
|
#define TEST_FROM_CD 0
|
|
#define WAIT_AFTER_STOP_STREAM 0 // Set to 1 if we need to wait for stream to clear after stopping a stream
|
|
|
|
namespace Pcm
|
|
{
|
|
|
|
static bool music_hed_there = true; // assume music and streams are there to start with, so we can load the hed
|
|
static bool streams_hed_there = true; // if hed is not there, then these get set to false
|
|
// which will disable all music and/or streams.
|
|
|
|
bool NoMusicPlease()
|
|
{
|
|
#if NO_MUSIC_PLEASE
|
|
return true;
|
|
#else
|
|
// Cannot have music 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 !music_hed_there;
|
|
#endif
|
|
}
|
|
|
|
bool StreamsDisabled()
|
|
{
|
|
#if DISABLE_STREAMS
|
|
return true;
|
|
#else
|
|
// Cannot have music 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 !streams_hed_there;
|
|
#endif
|
|
}
|
|
|
|
// prototypes...
|
|
bool TrackIsPlaying( int whichList, int whichTrack );
|
|
|
|
static int gLastStreamChannelPlayed = 0;
|
|
|
|
CurrentStreamInfo gCurrentStreamInfo[ NUM_STREAMS ];
|
|
|
|
TrackTitle PermTrackTitle[ MAX_NUM_TRACKS ];
|
|
|
|
// Static globals
|
|
static int current_music_track = 0;
|
|
static TrackList gTrackLists[ NUM_TRACKLISTS ];
|
|
static int gCurrentTrackList = TRACKLIST_PERM; // start out playing songs...
|
|
static char gTrackName[ MAX_TRACKNAME_STRING_LENGTH ];
|
|
static float gMusicVolume = DEFAULT_MUSIC_VOLUME;
|
|
static float gMusicStreamVolume = DEFAULT_MUSIC_STREAM_VOLUME;
|
|
static bool gMusicInRandomMode = true;
|
|
static bool gMusicLooping = false;
|
|
static int sCounter = 1;
|
|
static bool gPcmInitialized = false;
|
|
static int gNumStreams = 0;
|
|
static Obj::CStreamComponent *gpStreamingObj[ NUM_STREAMS ]; // should have as many of these as streams...
|
|
|
|
// Music stream data
|
|
|
|
static EMusicStreamType gMusicStreamType = MUSIC_STREAM_TYPE_NONE;
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
static uint32 gMusicStreamChecksum; // Checksum of music stream
|
|
static float gMusicStreamVolume; // Volume of music stream
|
|
static bool gMusicStreamWaitingToStart; // true if we are still waiting for the channel to become free
|
|
#endif // WAIT_AFTER_STOP_STREAM
|
|
|
|
// Limit is actually 500 ...
|
|
#define MAX_USER_SONGS 600
|
|
|
|
#ifdef __PLAT_XBOX__
|
|
static bool s_xbox_play_user_soundtracks = false;
|
|
static int s_xbox_user_soundtrack = 0;
|
|
static uint32 s_xbox_user_soundtrack_song = 0;
|
|
static bool s_xbox_user_soundtrack_random = false;
|
|
static uint32 s_xbox_random_index = 0;
|
|
|
|
static int sp_xbox_randomized_songs[MAX_USER_SONGS];
|
|
#endif
|
|
|
|
static bool shuffle_random_songs=true;
|
|
static int num_random_songs = 1;
|
|
static int random_song_index = 0;
|
|
static int sp_random_song_order[MAX_USER_SONGS];
|
|
|
|
|
|
// Stream ID functions
|
|
bool IDAvailable(uint32 id)
|
|
{
|
|
for ( int i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( (gCurrentStreamInfo[ i ].uniqueID == id) && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[ i ].voice) != PCM_STATUS_FREE))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int GetChannelFromID(uint32 id, bool assert_if_duplicate = true)
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( (gCurrentStreamInfo[ i ].uniqueID == id) && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[ i ].voice) != PCM_STATUS_FREE))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( (gCurrentStreamInfo[ i ].controlID == id) && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[ i ].voice) != PCM_STATUS_FREE))
|
|
{
|
|
Dbg_MsgAssert(!assert_if_duplicate, ("Trying to control duplicate stream %s by checksum", Script::FindChecksumName(id)));
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
uint32 GenerateUniqueID(uint32 id)
|
|
{
|
|
// Keep incrementing ID until one works.
|
|
while (!IDAvailable(id))
|
|
id++;
|
|
|
|
return id;
|
|
}
|
|
|
|
void StopMusic( void )
|
|
{
|
|
if (NoMusicPlease()) return;
|
|
|
|
// the counter has to reach SONG_UPDATE_INTERVAL before a new song is played...
|
|
// so resetting the counter keeps a new song from playing right away.
|
|
sCounter = 1;
|
|
gMusicStreamType = MUSIC_STREAM_TYPE_NONE; // In case we were in this mode
|
|
PCMAudio_StopMusic( true );
|
|
}
|
|
|
|
// If a channel is specified, stop a specific stream (might be a streaming soundFX on an object that's getting
|
|
// destroyed, for example).
|
|
// If channel parameter is -1, stop all non-music streams!
|
|
void StopStreams( int channel )
|
|
{
|
|
Replay::WriteStopStream(channel);
|
|
if (StreamsDisabled()) return;
|
|
|
|
Dbg_MsgAssert( ( channel >= -1 ) && ( channel < NUM_STREAMS ), ( "Invalid stream channel %d", channel ) );
|
|
if ( channel == -1 )
|
|
{
|
|
PCMAudio_StopStreams( );
|
|
}
|
|
else
|
|
{
|
|
PCMAudio_StopStream( channel );
|
|
}
|
|
}
|
|
|
|
// if uniqueID isn't specified, stop whatever stream is playing!
|
|
void StopStreamFromID( uint32 streamID )
|
|
{
|
|
if (StreamsDisabled()) return;
|
|
|
|
int channel = GetChannelFromID(streamID);
|
|
|
|
if (channel >= 0)
|
|
{
|
|
PCMAudio_StopStream( channel, true );
|
|
streamID = gCurrentStreamInfo[ channel ].uniqueID; // change over to uniqueID
|
|
}
|
|
|
|
for ( int i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( gpStreamingObj[ i ] )
|
|
{
|
|
for ( int j = 0; j < MAX_STREAMS_PER_OBJECT; j++ )
|
|
{
|
|
if ( gpStreamingObj[ i ]->mStreamingID[ j ] == streamID )
|
|
{
|
|
gpStreamingObj[ i ]->mStreamingID[ j ] = 0;
|
|
gpStreamingObj[ i ] = NULL;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool SetStreamVolumeFromID( uint32 streamID, Sfx::sVolume *p_volume )
|
|
{
|
|
if (StreamsDisabled()) return true;
|
|
|
|
int channel = GetChannelFromID(streamID);
|
|
|
|
if (channel >= 0)
|
|
{
|
|
# ifdef __PLAT_XBOX__
|
|
PCMAudio_SetStreamVolume( p_volume, channel );
|
|
# else
|
|
PCMAudio_SetStreamVolume( p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), channel );
|
|
# endif
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool SetStreamPitchFromID( uint32 streamID, float pitch )
|
|
{
|
|
if (StreamsDisabled()) return true;
|
|
|
|
int channel = GetChannelFromID(streamID);
|
|
|
|
if (channel >= 0)
|
|
{
|
|
PCMAudio_SetStreamPitch( pitch, channel );
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
#ifdef __PLAT_XBOX__
|
|
static void sGenerateRandomSongOrder( void )
|
|
{
|
|
int num_songs=Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack );
|
|
|
|
if( num_songs == 0 )
|
|
{
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
|
|
// Limit to MAX_USER_SONGS to prevent any buffer overwriting.
|
|
if (num_songs>MAX_USER_SONGS)
|
|
{
|
|
num_songs=MAX_USER_SONGS;
|
|
}
|
|
|
|
// Remember the last song in the previous order, so we can make sure that
|
|
// the first song in the new order is different from it.
|
|
// Note: OK if the last song was just an uninitialised random value, won't cause any problems.
|
|
int last_song=sp_xbox_randomized_songs[num_songs-1];
|
|
|
|
// Initialise the order to be 0,1,2,3 ... etc
|
|
for (int i=0; i<num_songs; ++i)
|
|
{
|
|
sp_xbox_randomized_songs[i]=i;
|
|
}
|
|
|
|
// Jumble it up.
|
|
for (int n=0; n<2000; ++n)
|
|
{
|
|
int a=Mth::Rnd(num_songs);
|
|
int b=Mth::Rnd(num_songs);
|
|
|
|
int temp=sp_xbox_randomized_songs[a];
|
|
sp_xbox_randomized_songs[a]=sp_xbox_randomized_songs[b];
|
|
sp_xbox_randomized_songs[b]=temp;
|
|
}
|
|
|
|
// If the first song in the new order equals the last song of the last order,
|
|
// do one further swap to make sure it is different.
|
|
if (sp_xbox_randomized_songs[0]==last_song)
|
|
{
|
|
// a is the first song.
|
|
int a=0;
|
|
|
|
// Choose b to be a random one of the other songs.
|
|
int b;
|
|
if (num_songs>1)
|
|
{
|
|
// Make b not able to equal 0, so that we don't swap the first with itself.
|
|
b=1+Mth::Rnd(num_songs-1);
|
|
}
|
|
else
|
|
{
|
|
// Unless there is only one song, in which case just swap it with itself. No point but what the heck.
|
|
b=0;
|
|
}
|
|
|
|
// Do the swap.
|
|
int temp=sp_xbox_randomized_songs[a];
|
|
sp_xbox_randomized_songs[a]=sp_xbox_randomized_songs[b];
|
|
sp_xbox_randomized_songs[b]=temp;
|
|
}
|
|
}
|
|
#endif // __PLAT_XBOX__
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void UseUserSoundtrack( int soundtrack )
|
|
{
|
|
# ifdef __PLAT_XBOX__
|
|
|
|
Dbg_MsgAssert(soundtrack>=0 && soundtrack<Pcm::GetNumSoundtracks(),("Bad soundtrack"));
|
|
|
|
s_xbox_play_user_soundtracks=true;
|
|
s_xbox_user_soundtrack=soundtrack;
|
|
if (s_xbox_user_soundtrack_random)
|
|
{
|
|
sGenerateRandomSongOrder();
|
|
s_xbox_random_index=0;
|
|
s_xbox_user_soundtrack_song=sp_xbox_randomized_songs[0];
|
|
}
|
|
else
|
|
{
|
|
s_xbox_user_soundtrack_song=0;
|
|
}
|
|
# endif // __PLAT_XBOX__
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void UseStandardSoundtrack( void )
|
|
{
|
|
# ifdef __PLAT_XBOX__
|
|
s_xbox_play_user_soundtracks = false;
|
|
# endif
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
bool UsingUserSoundtrack( void )
|
|
{
|
|
# ifdef __PLAT_XBOX__
|
|
return s_xbox_play_user_soundtracks;
|
|
# else
|
|
return false;
|
|
# endif
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void SaveSoundtrackToStructure( Script::CStruct *pStuff)
|
|
{
|
|
# ifdef __PLAT_XBOX__
|
|
Dbg_MsgAssert( pStuff,("NULL pStuff"));
|
|
|
|
if (s_xbox_play_user_soundtracks)
|
|
{
|
|
pStuff->AddComponent(Script::GenerateCRC("UserSoundtrackIndex"),ESYMBOLTYPE_INTEGER,s_xbox_user_soundtrack);
|
|
|
|
char p_buf[100];
|
|
const WCHAR* p_soundtrack_name_wide=Pcm::GetSoundtrackName(s_xbox_user_soundtrack);
|
|
if (p_soundtrack_name_wide)
|
|
{
|
|
wsprintfA( p_buf, "%ls", p_soundtrack_name_wide);
|
|
}
|
|
else
|
|
{
|
|
p_buf[0]=0;
|
|
}
|
|
|
|
pStuff->AddComponent(Script::GenerateCRC("UserSoundtrackName"),ESYMBOLTYPE_STRING,p_buf);
|
|
}
|
|
# endif // __PLAT_XBOX__
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void GetSoundtrackFromStructure( Script::CStruct *pStuff)
|
|
{
|
|
# ifdef __PLAT_XBOX__
|
|
Dbg_MsgAssert(pStuff,("NULL pStuff"));
|
|
|
|
int user_soundtrack_index=0;
|
|
if (pStuff->GetInteger("UserSoundtrackIndex",&user_soundtrack_index))
|
|
{
|
|
// A user soundtrack index is specified, but maybe the name does not match what is on this
|
|
// machine, ie, they could have put the mem card into a different xbox ...
|
|
|
|
// Get the name of this soundtrack on this machine
|
|
char p_buf[100];
|
|
const WCHAR* p_soundtrack_name_wide=Pcm::GetSoundtrackName(user_soundtrack_index);
|
|
if (p_soundtrack_name_wide)
|
|
{
|
|
wsprintfA( p_buf, "%ls", p_soundtrack_name_wide);
|
|
}
|
|
else
|
|
{
|
|
p_buf[0]=0;
|
|
}
|
|
|
|
// Get the name as stored on the mem card
|
|
const char *p_stored_name="";
|
|
pStuff->GetText("UserSoundtrackName",&p_stored_name);
|
|
|
|
if (strcmp(p_stored_name,p_buf)==0)
|
|
{
|
|
// They match! So use that soundtrack.
|
|
UseUserSoundtrack(user_soundtrack_index);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Oh well, use the standard soundtrack instead.
|
|
UseStandardSoundtrack();
|
|
# endif // __PLAT_XBOX__
|
|
}
|
|
|
|
|
|
|
|
bool _PlayMusicTrack( const char *filename, float volume )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling playtrack %s, when PCM Audio not initialized.", filename ));
|
|
Dbg_MsgAssert( strlen( filename ) < 200,( "Filename too long" ));
|
|
|
|
# ifdef __PLAT_XBOX__
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if( s_xbox_play_user_soundtracks && !skate_mod->GetGameMode()->IsFrontEnd())
|
|
{
|
|
if( Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack ) == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !PCMAudio_PlaySoundtrackMusicTrack( s_xbox_user_soundtrack, s_xbox_user_soundtrack_song ))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if( s_xbox_user_soundtrack_random )
|
|
{
|
|
++s_xbox_random_index;
|
|
if( s_xbox_random_index >= Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack ))
|
|
{
|
|
sGenerateRandomSongOrder();
|
|
s_xbox_random_index = 0;
|
|
}
|
|
s_xbox_user_soundtrack_song=sp_xbox_randomized_songs[s_xbox_random_index];
|
|
}
|
|
else
|
|
{
|
|
++s_xbox_user_soundtrack_song;
|
|
if( s_xbox_user_soundtrack_song >= Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack ))
|
|
{
|
|
s_xbox_user_soundtrack_song = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !PCMAudio_PlayMusicTrack( filename ))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
# else
|
|
if( !PCMAudio_PlayMusicTrack( filename ))
|
|
{
|
|
return false;
|
|
}
|
|
# endif // __PLAT_XBOX__
|
|
|
|
// volume = volume * Config::GetMasterVolume()/100.0f;
|
|
// if (volume <0.1f)
|
|
// {
|
|
// volume = 0.0f;
|
|
// }
|
|
PCMAudio_SetMusicVolume( volume );
|
|
return ( true );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool _PlayMusicStream( uint32 checksum, float volume )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling playtrack %s, when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
|
|
|
|
#ifdef __PLAT_NGPS__
|
|
if( !PCMAudio_PlayMusicStream( checksum ))
|
|
{
|
|
Dbg_MsgAssert(0, ( "Failed playing track %s", Script::FindChecksumName(checksum) ) );
|
|
Dbg_Message( "Failed playing track %s", Script::FindChecksumName(checksum) );
|
|
return false;
|
|
}
|
|
#else
|
|
Dbg_MsgAssert(0, ("PCMAudio_PlayMusicStream() not implemented on this platform."));
|
|
#endif // __PLAT_NGPS__
|
|
|
|
PCMAudio_SetMusicVolume( volume );
|
|
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
// returns -1 on failure, otherwise the index into which voice played the sound.
|
|
int _PlayStream( uint32 checksum, Sfx::sVolume *p_volume, float pitch, int priority, uint32 controlID, bool preload_only )
|
|
{
|
|
if (StreamsDisabled()) return -1;
|
|
|
|
# ifdef __PLAT_XBOX__
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling _PlayStream(), when PCM Audio not initialized." ));
|
|
# endif // __PLAT_XBOX__
|
|
|
|
// Initialize lowest to a valid entry
|
|
int lowest_priority = gCurrentStreamInfo[ 0 ].priority;
|
|
int lowest_priority_channel = 0;
|
|
uint32 lowest_priority_start_frame = gCurrentStreamInfo[ 0 ].start_frame;
|
|
|
|
bool success;
|
|
|
|
for ( int i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( PCMAudio_GetStreamStatus( i ) == PCM_STATUS_FREE )
|
|
{
|
|
if (preload_only)
|
|
{
|
|
success = PCMAudio_PreLoadStream( checksum, i );
|
|
}
|
|
else
|
|
{
|
|
// success = PCMAudio_PlayStream( checksum, i, volumeL, volumeR, pitch );
|
|
# ifdef __PLAT_XBOX__
|
|
success = PCMAudio_PlayStream( checksum, i, p_volume, pitch );
|
|
# else
|
|
success = PCMAudio_PlayStream( checksum, i, p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), pitch );
|
|
# endif
|
|
}
|
|
|
|
if ( !success )
|
|
{
|
|
return ( -1 );
|
|
}
|
|
|
|
gCurrentStreamInfo[ i ].controlID = controlID;
|
|
gCurrentStreamInfo[ i ].uniqueID = GenerateUniqueID(controlID);
|
|
gCurrentStreamInfo[ i ].voice = i;
|
|
gCurrentStreamInfo[ i ].priority = priority;
|
|
gCurrentStreamInfo[ i ].start_frame = (uint32)Tmr::GetRenderFrame();
|
|
gCurrentStreamInfo[ i ].p_frame_amp = CStreamFrameAmpManager::sGetFrameAmp(checksum);
|
|
return ( i );
|
|
}
|
|
else if ( gCurrentStreamInfo[ i ].priority <= lowest_priority )
|
|
{
|
|
// If it is equal priority, we want to find the oldest one
|
|
if ((gCurrentStreamInfo[ i ].priority == lowest_priority) &&
|
|
(gCurrentStreamInfo[ i ].start_frame >= lowest_priority_start_frame))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
lowest_priority = gCurrentStreamInfo[ i ].priority;
|
|
lowest_priority_channel = i;
|
|
lowest_priority_start_frame = gCurrentStreamInfo[ i ].start_frame;
|
|
}
|
|
}
|
|
|
|
// Couldn't find a free one, so lets see if we can knock one off
|
|
if (priority >= lowest_priority)
|
|
{
|
|
PCMAudio_StopStream( lowest_priority_channel );
|
|
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
Dbg_MsgAssert(0, ("Priority streams not implemented on this system")); // Not implemented
|
|
return ( -1 );
|
|
#else
|
|
if (preload_only)
|
|
{
|
|
success = PCMAudio_PreLoadStream( checksum, lowest_priority_channel );
|
|
}
|
|
else
|
|
{
|
|
// success = PCMAudio_PlayStream( checksum, lowest_priority_channel, volumeL, volumeR, pitch );
|
|
# ifdef __PLAT_XBOX__
|
|
success = PCMAudio_PlayStream( checksum, lowest_priority_channel, p_volume, pitch );
|
|
# else
|
|
success = PCMAudio_PlayStream( checksum, lowest_priority_channel, p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), pitch );
|
|
# endif
|
|
}
|
|
|
|
if ( !success )
|
|
{
|
|
Dbg_MsgAssert(0, ("Higher priority stream could not start. Make sure there was another error (like file not found)."));
|
|
return ( -1 );
|
|
}
|
|
|
|
gCurrentStreamInfo[ lowest_priority_channel ].controlID = controlID;
|
|
gCurrentStreamInfo[ lowest_priority_channel ].uniqueID = GenerateUniqueID(controlID);
|
|
gCurrentStreamInfo[ lowest_priority_channel ].voice = lowest_priority_channel;
|
|
gCurrentStreamInfo[ lowest_priority_channel ].priority = priority;
|
|
gCurrentStreamInfo[ lowest_priority_channel ].start_frame = (uint32)Tmr::GetRenderFrame();
|
|
gCurrentStreamInfo[ lowest_priority_channel ].p_frame_amp = CStreamFrameAmpManager::sGetFrameAmp(checksum);
|
|
#endif // WAIT_AFTER_STOP_STREAM
|
|
return ( lowest_priority_channel );
|
|
}
|
|
|
|
return ( -1 );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool _PreLoadMusicStream( uint32 checksum )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling _PreLoadMusicStream %s, when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
|
|
|
|
return PCMAudio_PreLoadMusicStream( checksum );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool _StartPreLoadedMusicStream( float volume )
|
|
{
|
|
if( !PCMAudio_StartPreLoadedMusicStream( ))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PCMAudio_SetMusicVolume( volume );
|
|
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
#define MIN_STREAM_VOL 2.0f
|
|
|
|
|
|
|
|
uint32 PlayStreamFromObject( Obj::CStreamComponent *pComponent, uint32 streamNameChecksum, float dropoff, float volume,
|
|
float pitch, int priority, int use_pos_info, EDropoffFunc dropoffFunc, uint32 controlID )
|
|
{
|
|
Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
|
|
|
|
// Don't start a stream if it won't be heard
|
|
if( sfx_manager->PositionalSoundSilenceMode() )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Replay::WritePositionalStream(pComponent->GetObject()->GetID(),streamNameChecksum,dropoff,volume,pitch,priority,use_pos_info);
|
|
if (StreamsDisabled()) return 0;
|
|
|
|
Sfx::sVolume vol;
|
|
vol.SetSilent();
|
|
|
|
// Let the priority system figure this out
|
|
//if (!StreamAvailable())
|
|
// return 0;
|
|
|
|
// Set up volume according to position of object to camera.
|
|
if ( use_pos_info != 0 )
|
|
{
|
|
Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pComponent->GetObject()->m_pos );
|
|
|
|
if (pCamera)
|
|
{
|
|
Mth::Vector dropoff_pos;
|
|
Mth::Vector *p_dropoff_pos = NULL;
|
|
if (pComponent->GetClosestDropoffPos(pCamera, dropoff_pos))
|
|
{
|
|
p_dropoff_pos = &dropoff_pos;
|
|
}
|
|
|
|
sfx_manager->SetVolumeFromPos( &vol, pComponent->GetClosestEmitterPos(pCamera), dropoff, dropoffFunc, pCamera, p_dropoff_pos );
|
|
}
|
|
else
|
|
{
|
|
vol.SetSilent();
|
|
}
|
|
|
|
// Seems strange to cancel out a stream if it starting low
|
|
if( fabsf( vol.GetLoudestChannel()) < MIN_STREAM_VOL )
|
|
{
|
|
if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
|
|
{
|
|
Dbg_Message("I wanted to cancel stream %s", Script::FindChecksumName(streamNameChecksum));
|
|
}
|
|
// return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vol.SetChannelVolume( 0, volume );
|
|
vol.SetChannelVolume( 1, volume );
|
|
}
|
|
|
|
// K: The 'false' on the end means do not record the call in the replay, because this
|
|
// call to PlayStreamFromObject has already been recorded.
|
|
uint32 uniqueID = PlayStream( streamNameChecksum, &vol, pitch, priority, controlID, false );
|
|
if ( uniqueID )
|
|
{
|
|
Dbg_MsgAssert( ( ( gLastStreamChannelPlayed >= 0 ) && ( gLastStreamChannelPlayed < MAX_STREAMS_PER_OBJECT ) ), ( "Tell Matt to fix object streams" ) );
|
|
if ( gpStreamingObj[ gLastStreamChannelPlayed ] )
|
|
{
|
|
// the object might not have called StreamUpdate yet, so the slot might
|
|
// still think it's in use... clear the slot if that's the case:
|
|
gpStreamingObj[ gLastStreamChannelPlayed ]->mStreamingID[ gLastStreamChannelPlayed ] = 0;
|
|
gpStreamingObj[ gLastStreamChannelPlayed ] = NULL;
|
|
}
|
|
// Dbg_MsgAssert( !gpStreamingObj[ gLastStreamChannelPlayed ], ( "Have Matt fix object streams... Kick him in the nutsack while you're at it." ) );
|
|
// Dbg_MsgAssert( !pObject->mStreamingID[ gLastStreamChannelPlayed ], ( "Have Matt fix object streams... Hell, fire matt!!!" ) );
|
|
gpStreamingObj[ gLastStreamChannelPlayed ] = pComponent;
|
|
pComponent->mStreamingID[ gLastStreamChannelPlayed ] = uniqueID;
|
|
// if volume modifier sent in, store that:
|
|
if ( volume != 100.0f )
|
|
{
|
|
int i;
|
|
for ( i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( gCurrentStreamInfo[ i ].uniqueID == uniqueID )
|
|
{
|
|
gCurrentStreamInfo[ i ].volume = PERCENT( gCurrentStreamInfo[ i ].volume, volume );
|
|
gCurrentStreamInfo[ i ].dropoff = dropoff;
|
|
}
|
|
}
|
|
}
|
|
|
|
// store state of use_pos_info
|
|
if ( !use_pos_info )
|
|
{
|
|
for ( int i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( gCurrentStreamInfo[i].uniqueID == uniqueID )
|
|
{
|
|
gCurrentStreamInfo[i].use_pos_info = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( int i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( gCurrentStreamInfo[i].uniqueID == uniqueID )
|
|
{
|
|
gCurrentStreamInfo[i].use_pos_info = true;
|
|
gCurrentStreamInfo[i].dropoffFunction = dropoffFunc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ( uniqueID );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
uint32 PlayStream( uint32 checksum, Sfx::sVolume *p_volume, float pitch, int priority, uint32 controlID, bool record_in_replay )
|
|
{
|
|
if (record_in_replay)
|
|
{
|
|
Replay::WritePlayStream( checksum, p_volume, pitch, priority );
|
|
}
|
|
if (StreamsDisabled()) return 0;
|
|
|
|
// Just use checksum if no controlID is set
|
|
if (controlID == 0)
|
|
{
|
|
controlID = checksum;
|
|
}
|
|
|
|
if (!IDAvailable(controlID))
|
|
{
|
|
Dbg_Message("Playing stream %s with same checksum as one already being played, won't be able to control directly.", Script::FindChecksumName(controlID));
|
|
}
|
|
|
|
int whichStream;
|
|
|
|
// allow streams to play with default values, if not pre-loaded.
|
|
whichStream = _PlayStream( checksum, p_volume, pitch, priority, controlID, false );
|
|
if ( whichStream != -1 )
|
|
{
|
|
gCurrentStreamInfo[ whichStream ].dropoff = DEFAULT_STREAM_DROPOFF;
|
|
gCurrentStreamInfo[ whichStream ].volume = 100.0f;
|
|
gLastStreamChannelPlayed = whichStream;
|
|
return gCurrentStreamInfo[ whichStream ].uniqueID;
|
|
}
|
|
|
|
// Dbg_MsgAssert( 0,( "Tried to play track %s not in header.", Script::FindChecksumName( checksum ) ));
|
|
return ( 0 );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
uint32 PreLoadStream( uint32 checksum, int priority )
|
|
{
|
|
if (StreamsDisabled()) return 0;
|
|
|
|
// Since this isn't hooked up to a script, we just supply the checksum
|
|
uint32 controlID = checksum;
|
|
|
|
int whichStream;
|
|
|
|
// allow streams to play with default values, if not in list.
|
|
Sfx::sVolume vol;
|
|
vol.SetSilent();
|
|
whichStream = _PlayStream( checksum, &vol, 0.0, priority, controlID, true );
|
|
|
|
if ( whichStream != -1 )
|
|
{
|
|
#ifdef __PLAT_NGPS__
|
|
Dbg_Message("PreLoadStream for stream %s on channel %d", Script::FindChecksumName( checksum ), whichStream);
|
|
#endif
|
|
gCurrentStreamInfo[ whichStream ].dropoff = DEFAULT_STREAM_DROPOFF;
|
|
gCurrentStreamInfo[ whichStream ].volume = 100.0f;
|
|
return gCurrentStreamInfo[ whichStream ].uniqueID;
|
|
}
|
|
|
|
// Dbg_MsgAssert( 0,( "Tried to play track %s not in header.", Script::FindChecksumName( checksum ) ));
|
|
return ( 0 );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool PreLoadStreamDone( uint32 streamID )
|
|
{
|
|
if (StreamsDisabled()) {
|
|
return false;
|
|
}
|
|
|
|
int channel = GetChannelFromID(streamID);
|
|
|
|
if ( channel >= 0 )
|
|
{
|
|
#ifdef __PLAT_NGPS__
|
|
if (PCMAudio_PreLoadStreamDone( channel ))
|
|
{
|
|
Dbg_Message("PreLoadStreamDone for stream %s on channel %d is true", Script::FindChecksumName( streamID ), channel );
|
|
}
|
|
#endif
|
|
return PCMAudio_PreLoadStreamDone( channel );
|
|
}
|
|
|
|
// Couldn't find stream
|
|
return false;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
bool StartPreLoadedStream( uint32 streamID, Sfx::sVolume *p_volume, float pitch )
|
|
{
|
|
if (StreamsDisabled()) return false;
|
|
|
|
int channel = GetChannelFromID(streamID);
|
|
|
|
if ( channel >= 0 )
|
|
{
|
|
#ifdef __PLAT_NGPS__
|
|
Dbg_Message("StartPreLoadStream for stream %s on channel %d", Script::FindChecksumName( streamID ), channel);
|
|
#endif
|
|
|
|
// return PCMAudio_StartPreLoadedStream( channel, volumeL, volumeR, pitch );
|
|
# ifdef __PLAT_XBOX__
|
|
return PCMAudio_StartPreLoadedStream( channel, p_volume, pitch );
|
|
# else
|
|
return PCMAudio_StartPreLoadedStream( channel, p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), pitch );
|
|
# endif
|
|
}
|
|
|
|
// Couldn't find stream
|
|
return false;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool PreLoadMusicStream( uint32 checksum )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::PlayMusicStream '%s', when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
|
|
Dbg_MsgAssert( !gMusicStreamType,( "Calling Pcm::PlayMusicStream '%s', when one is already playing.", Script::FindChecksumName(checksum) ));
|
|
|
|
// Stop any previous track
|
|
PCMAudio_StopMusic(false);
|
|
|
|
bool success = false;
|
|
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
gMusicStreamChecksum = checksum;
|
|
gMusicStreamWaitingToStart = true;
|
|
#else
|
|
// allow streams to play with default values, if not in list.
|
|
success = _PreLoadMusicStream( checksum );
|
|
#endif // WAIT_AFTER_STOP_STREAM
|
|
|
|
gMusicStreamType = MUSIC_STREAM_TYPE_PRELOAD;
|
|
|
|
return success;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool PreLoadMusicStreamDone( void )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
Dbg_MsgAssert( gMusicStreamType == MUSIC_STREAM_TYPE_PRELOAD,( "Calling Pcm::PreLoadMusicStreamDone while in normal music mode." ));
|
|
|
|
return PCMAudio_PreLoadMusicStreamDone( );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool StartPreLoadedMusicStream( float volume )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
|
|
Dbg_MsgAssert( gMusicStreamType == MUSIC_STREAM_TYPE_PRELOAD,( "Calling Pcm::StartPreLoadedMusicStream while in normal music mode." ));
|
|
|
|
if ( volume < 0.0f )
|
|
{
|
|
volume = gMusicStreamVolume;
|
|
}
|
|
|
|
return _StartPreLoadedMusicStream( volume );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool SetStreamVolume( int objStreamIndex )
|
|
{
|
|
if ( StreamsDisabled() ) return false;
|
|
|
|
Obj::CStreamComponent *pComp;
|
|
pComp = gpStreamingObj[ objStreamIndex ];
|
|
|
|
CurrentStreamInfo *pInfo = NULL;
|
|
|
|
int i;
|
|
for ( i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( gCurrentStreamInfo[ i ].uniqueID == pComp->mStreamingID[ objStreamIndex ] )
|
|
{
|
|
if ( PCMAudio_GetStreamStatus( gCurrentStreamInfo[ objStreamIndex ].voice ) == PCM_STATUS_FREE )
|
|
{
|
|
return ( false );
|
|
}
|
|
pInfo = &gCurrentStreamInfo[ i ];
|
|
|
|
// Update volume from dropoff/pan for the object responsible for streaming.
|
|
Sfx::sVolume vol;
|
|
|
|
Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
|
|
if ( pInfo->use_pos_info )
|
|
{
|
|
Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pComp->GetObject()->m_pos );
|
|
|
|
if (pCamera)
|
|
{
|
|
Mth::Vector dropoff_pos;
|
|
Mth::Vector *p_dropoff_pos = NULL;
|
|
if (pComp->GetClosestDropoffPos(pCamera, dropoff_pos))
|
|
{
|
|
p_dropoff_pos = &dropoff_pos;
|
|
}
|
|
|
|
sfx_manager->SetVolumeFromPos( &vol, pComp->GetClosestEmitterPos(pCamera), pInfo->dropoff, pInfo->dropoffFunction, pCamera, p_dropoff_pos );
|
|
}
|
|
else
|
|
{
|
|
vol.SetSilent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vol.SetChannelVolume( 0, pInfo->volume );
|
|
vol.SetChannelVolume( 1, pInfo->volume );
|
|
}
|
|
|
|
if( pInfo->volume != 100.0f )
|
|
{
|
|
vol.PercentageAdjustment( pInfo->volume );
|
|
}
|
|
|
|
# ifdef __PLAT_XBOX__
|
|
// We need the sign-correct version for Xbox to calculate proper 5.1 values.
|
|
PCMAudio_SetStreamVolume( &vol, pInfo->voice );
|
|
# else
|
|
PCMAudio_SetStreamVolume( vol.GetChannelVolume( 0 ), vol.GetChannelVolume( 1 ), pInfo->voice );
|
|
# endif
|
|
return ( true );
|
|
}
|
|
}
|
|
return ( false );
|
|
}
|
|
|
|
void Init( void )
|
|
{
|
|
// Zero these out at startup.
|
|
for( int s = 0; s < NUM_STREAMS; ++s )
|
|
{
|
|
gpStreamingObj[s] = NULL;
|
|
}
|
|
|
|
if (NoMusicPlease() && StreamsDisabled()) return;
|
|
|
|
PCMAudio_Init( );
|
|
gPcmInitialized = true;
|
|
gMusicVolume = (float)Script::GetInteger( 0xabd4a575 ); // checksum 'MusicVolume'
|
|
if ( !gMusicVolume )
|
|
{
|
|
gMusicVolume = DEFAULT_MUSIC_VOLUME;
|
|
}
|
|
// SetVolume( gMusicVolume );
|
|
gMusicStreamVolume = (float)Script::GetInteger(CRCD(0x73f8e03b,"MusicStreamVolume"));
|
|
if ( !gMusicStreamVolume )
|
|
{
|
|
gMusicStreamVolume = DEFAULT_MUSIC_STREAM_VOLUME;
|
|
}
|
|
|
|
int i;
|
|
for ( i = 0; i < NUM_TRACKLISTS; i++ )
|
|
{
|
|
gTrackLists[ i ].numTracks = 0;
|
|
gTrackLists[ i ].trackForbidden0 = 0;
|
|
gTrackLists[ i ].trackForbidden1 = 0;
|
|
gTrackLists[ i ].trackPlayed0 = 0;
|
|
gTrackLists[ i ].trackPlayed1 = 0;
|
|
// no tracks, so all tracks forbidden, right?
|
|
gTrackLists[ i ].allTracksForbidden = true;
|
|
}
|
|
for ( i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
gpStreamingObj[ i ] = NULL;
|
|
gCurrentStreamInfo[ i ].controlID = 0;
|
|
gCurrentStreamInfo[ i ].uniqueID = 0;
|
|
}
|
|
} // end of Init( )
|
|
|
|
|
|
// Should be renamed to StopUsingCD( ) as if the cd is in use
|
|
// by streams, this function stops the streams and frees up the
|
|
// CD.
|
|
bool UsingCD( void )
|
|
{
|
|
|
|
#if defined( __PLAT_NGC__ )
|
|
if ( !( gPcmInitialized ) )
|
|
{
|
|
return ( false );
|
|
}
|
|
StopMusic( );
|
|
StopStreams( );
|
|
|
|
#else
|
|
|
|
if (Config::CD() || TEST_FROM_CD)
|
|
{
|
|
if ( !( gPcmInitialized ) )
|
|
{
|
|
// Dbg_Message( "uninitialized" );
|
|
return ( false );
|
|
}
|
|
StopMusic( );
|
|
StopStreams( );
|
|
}
|
|
#endif
|
|
|
|
return ( false );
|
|
} // end of UsingCD( )
|
|
|
|
// plays a music track.
|
|
void PlayTrack( const char *filename, bool loop )
|
|
{
|
|
if (NoMusicPlease()) return;
|
|
|
|
if (gMusicStreamType)
|
|
{
|
|
Dbg_MsgAssert(0, ("Trying to play a music track in stereo stream mode."));
|
|
return;
|
|
}
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::PlayTrack '%s', when PCM Audio not initialized.", filename ));
|
|
Dbg_MsgAssert( strlen( filename ) < MAX_TRACKNAME_STRING_LENGTH,( "CD Audio Filename too long." ));
|
|
strcpy( gTrackName, filename );
|
|
|
|
int trackList = gCurrentTrackList;
|
|
float volume;
|
|
if ( trackList == TRACKLIST_PERM )
|
|
{
|
|
volume = gMusicVolume;
|
|
}
|
|
else
|
|
{
|
|
Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
|
|
volume = sfx_manager->GetMainVolume( );
|
|
}
|
|
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
|
|
{
|
|
trackList = TRACKLIST_LEVEL_SPECIFIC;
|
|
volume = gMusicVolume;
|
|
}
|
|
|
|
SetLoopingMode(loop);
|
|
|
|
if ( !_PlayMusicTrack( gTrackName, volume ) )
|
|
{
|
|
if (Config::CD() || TEST_FROM_CD)
|
|
{
|
|
Dbg_Message( "Failed playing track %s", filename );
|
|
}
|
|
else
|
|
{
|
|
// see if this is a track in the current playlists...
|
|
// if so, don't keep trying to play it...
|
|
TrackList *pTrackList = &gTrackLists[ trackList ];
|
|
int i;
|
|
for ( i = 0; i < pTrackList->numTracks; i++ )
|
|
{
|
|
if ( Script::GenerateCRC( filename ) == Script::GenerateCRC( pTrackList->trackInfo[ i ].trackName ) )
|
|
{
|
|
SetTrackForbiddenStatus( i, true, trackList );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Plays a stereo stream through the music channels
|
|
void PlayMusicStream( uint32 checksum, float volume )
|
|
{
|
|
if (NoMusicPlease()) return;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::PlayMusicStream '%s', when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
|
|
Dbg_MsgAssert( !gMusicStreamType,( "Calling Pcm::PlayMusicStream '%s', when one is already playing.", Script::FindChecksumName(checksum) ));
|
|
|
|
// Stop any previous track
|
|
PCMAudio_StopMusic(false);
|
|
|
|
if ( volume < 0.0f )
|
|
{
|
|
volume = gMusicStreamVolume;
|
|
}
|
|
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
gMusicStreamChecksum = checksum;
|
|
gMusicStreamVolume = volume;
|
|
gMusicStreamWaitingToStart = true;
|
|
#else
|
|
_PlayMusicStream( checksum, volume );
|
|
#endif
|
|
|
|
gMusicStreamType = MUSIC_STREAM_TYPE_NORMAL;
|
|
}
|
|
|
|
static bool sMusicIsPaused=false;
|
|
void PauseMusic( int pause )
|
|
{
|
|
|
|
|
|
static bool wasPaused = false;
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::Pause when PCM Audio not initialized." ));
|
|
switch ( pause )
|
|
{
|
|
case ( -1 ):
|
|
// special case... unpause only if it was unpaused last time pause was called...
|
|
if ( !wasPaused )
|
|
{
|
|
// recursive... unpause this bitch...
|
|
PauseMusic( 0 );
|
|
return;
|
|
}
|
|
break;
|
|
case ( 1 ):
|
|
wasPaused = sMusicIsPaused;
|
|
sMusicIsPaused = true;
|
|
PCMAudio_Pause( true, MUSIC_CHANNEL );
|
|
break;
|
|
case ( 0 ):
|
|
sMusicIsPaused = false;
|
|
PCMAudio_Pause( false, MUSIC_CHANNEL );
|
|
break;
|
|
default:
|
|
Dbg_MsgAssert( 0, ( "Unknown pause state %d", pause ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool MusicIsPaused()
|
|
{
|
|
return sMusicIsPaused;
|
|
}
|
|
|
|
void PauseStream( int pause )
|
|
{
|
|
|
|
|
|
static bool paused = false;
|
|
static bool wasPaused = false;
|
|
|
|
if (StreamsDisabled()) return;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::Pause when PCM Audio not initialized." ));
|
|
switch ( pause )
|
|
{
|
|
case ( -1 ):
|
|
// special case... unpause only if it was unpaused last time pause was called...
|
|
if ( !wasPaused )
|
|
{
|
|
// recursive... unpause this bitch...
|
|
PauseStream( 0 );
|
|
return;
|
|
}
|
|
break;
|
|
case ( 1 ):
|
|
wasPaused = paused;
|
|
paused = true;
|
|
PCMAudio_Pause( true, EXTRA_CHANNEL );
|
|
break;
|
|
case ( 0 ):
|
|
paused = false;
|
|
PCMAudio_Pause( false, EXTRA_CHANNEL );
|
|
break;
|
|
default:
|
|
Dbg_MsgAssert( 0, ( "Unknown pause state %d", pause ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SetVolume( float volume )
|
|
{
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::SetVolume, when PCM Audio not initialized." ));
|
|
if ( ( volume > 100.0f ) || ( volume < 0.0f ) )
|
|
{
|
|
Dbg_MsgAssert( 0,( "Volume (%f) not between 0 and 100", volume ));
|
|
return;
|
|
}
|
|
|
|
|
|
gMusicVolume = volume;
|
|
if ( gPcmInitialized )
|
|
{
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( ( gMusicStreamType == MUSIC_STREAM_TYPE_NONE) &&
|
|
( ( gCurrentTrackList == TRACKLIST_PERM ) ||
|
|
( skate_mod->GetGameMode( )->IsFrontEnd( ) ) ) )
|
|
{
|
|
// volume = volume * Config::GetMasterVolume()/100.0f;
|
|
// if (volume <0.1f)
|
|
// {
|
|
// volume = 0.0f;
|
|
// }
|
|
PCMAudio_SetMusicVolume( volume );
|
|
}
|
|
}
|
|
}
|
|
|
|
float GetVolume( void )
|
|
{
|
|
|
|
return ( gMusicVolume );
|
|
}
|
|
|
|
void SetMusicStreamVolume( float volume )
|
|
{
|
|
if (NoMusicPlease()) return;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::SetVolume, when PCM Audio not initialized." ));
|
|
if ( ( volume > 100.0f ) || ( volume < 0.0f ) )
|
|
{
|
|
Dbg_MsgAssert( 0,( "Volume (%f) not between 0 and 100", volume ));
|
|
return;
|
|
}
|
|
|
|
gMusicStreamVolume = volume;
|
|
if ( gPcmInitialized )
|
|
{
|
|
if (gMusicStreamType == MUSIC_STREAM_TYPE_NORMAL)
|
|
{
|
|
PCMAudio_SetMusicVolume( volume );
|
|
}
|
|
}
|
|
}
|
|
|
|
float GetMusicStreamVolume( void )
|
|
{
|
|
return gMusicStreamVolume;
|
|
}
|
|
|
|
void AddTrackToPlaylist( const char *trackName, int whichList, const char *trackTitle )
|
|
{
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
|
|
Dbg_MsgAssert( trackName,( "What the fuck?" ));
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
|
|
TrackList *pTrackList = &gTrackLists[ whichList ];
|
|
|
|
// Ken: This is a fix to TT6484. When an ambient track finished, it was choosing
|
|
// a new random ambient track, rather than playing the same one again.
|
|
// So to fix, just force numTracks to zero if adding an ambient track, so that there
|
|
// can be only one. That way, it will be forced to play the same one again. Nya ha!
|
|
if (whichList==TRACKLIST_LEVEL_SPECIFIC)
|
|
{
|
|
pTrackList->numTracks=0;
|
|
}
|
|
|
|
if ( pTrackList->numTracks >= MAX_NUM_TRACKS )
|
|
{
|
|
Dbg_MsgAssert( 0,( "Too many tracks in playlist (increase MAX_NUM_TRACKS or clear list)." ));
|
|
return;
|
|
}
|
|
Dbg_MsgAssert( strlen( trackName ) < MAX_TRACKNAME_STRING_LENGTH,( "Track name too long." ));
|
|
char *pTrackName = pTrackList->trackInfo[ pTrackList->numTracks ].trackName;
|
|
strcpy( pTrackName, trackName );
|
|
|
|
uint i;
|
|
for ( i = 0; i < strlen( pTrackName ); i++ )
|
|
{
|
|
if ( ( pTrackName[ i ] >= 'a' ) && ( pTrackName[ i ] <= 'z' ) )
|
|
{
|
|
pTrackName[ i ] = pTrackName[ i ] - 'a' + 'A';
|
|
}
|
|
}
|
|
// Music tracks should have a title (for the user to be able to turn songs on/off):
|
|
if ( whichList == TRACKLIST_PERM )
|
|
{
|
|
char *pTrackTitle = PermTrackTitle[ pTrackList->numTracks ].trackTitle;
|
|
if ( trackTitle )
|
|
{
|
|
Dbg_MsgAssert( strlen( trackTitle ) < TRACK_TITLE_MAX_SIZE, ( "Track title %s too long (max %d chars)", trackTitle, TRACK_TITLE_MAX_SIZE ) );
|
|
strncpy( pTrackTitle, trackTitle, TRACK_TITLE_MAX_SIZE );
|
|
pTrackTitle[ TRACK_TITLE_MAX_SIZE - 1 ] = '\0';
|
|
}
|
|
else
|
|
{
|
|
Dbg_MsgAssert( 0, ( "Track %s not given a title.", trackName ) );
|
|
strcpy( pTrackTitle, "No title provided" );
|
|
}
|
|
}
|
|
if ( !PCMAudio_TrackExists( pTrackName, MUSIC_CHANNEL ) )
|
|
{
|
|
Dbg_MsgAssert(!Config::CD(),("Could not find music track '%s'\n",pTrackName));
|
|
Dbg_Message("Could not find music track '%s'",pTrackName);
|
|
return;
|
|
}
|
|
pTrackList->allTracksForbidden = false;
|
|
pTrackList->numTracks++;
|
|
}
|
|
|
|
void ClearPlaylist( int whichList )
|
|
{
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbshit." ));
|
|
TrackList *pTrackList = &gTrackLists[ whichList ];
|
|
pTrackList->numTracks = 0;
|
|
pTrackList->allTracksForbidden = true; // if there are no tracks, all are forbidden, no?
|
|
pTrackList->trackForbidden0 = 0;
|
|
pTrackList->trackForbidden1 = 0;
|
|
pTrackList->trackPlayed0 = 0;
|
|
pTrackList->trackPlayed1 = 0;
|
|
|
|
gNumStreams = 0;
|
|
}
|
|
|
|
void SkipMusicTrack( void )
|
|
{
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( gCurrentTrackList == TRACKLIST_PERM )
|
|
{
|
|
// will stop the current song, auto update will
|
|
// cause music to continue with the next track...
|
|
StopMusic( );
|
|
}
|
|
}
|
|
|
|
void SetRandomMode( int randomModeOn )
|
|
{
|
|
if (NoMusicPlease()) return;
|
|
|
|
gMusicInRandomMode = randomModeOn;
|
|
|
|
# ifdef __PLAT_XBOX__
|
|
s_xbox_user_soundtrack_random = ( randomModeOn > 0 );
|
|
if( randomModeOn )
|
|
{
|
|
// Generate a new random order now, since the current one may be invalid for this soundtrack.
|
|
sGenerateRandomSongOrder();
|
|
s_xbox_random_index = 0;
|
|
s_xbox_user_soundtrack_song = sp_xbox_randomized_songs[0];
|
|
}
|
|
# endif // __PLAT_XBOX__
|
|
|
|
Dbg_MsgAssert(gCurrentTrackList>=0 && gCurrentTrackList<NUM_TRACKLISTS,("Bad gCurrentTrackList"));
|
|
TrackList *pTrackList = &gTrackLists[ gCurrentTrackList ];
|
|
pTrackList->trackPlayed0=0;
|
|
pTrackList->trackPlayed1=0;
|
|
}
|
|
|
|
int GetRandomMode( void )
|
|
{
|
|
return ( gMusicInRandomMode );
|
|
}
|
|
|
|
void SetLoopingMode( bool loop )
|
|
{
|
|
gMusicLooping = loop;
|
|
}
|
|
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
static void _StartMusicStream()
|
|
{
|
|
switch (gMusicStreamType)
|
|
{
|
|
case MUSIC_STREAM_TYPE_NORMAL:
|
|
_PlayMusicStream( gMusicStreamChecksum, gMusicStreamVolume );
|
|
break;
|
|
|
|
case MUSIC_STREAM_TYPE_PRELOAD:
|
|
_PreLoadMusicStream( gMusicStreamChecksum );
|
|
break;
|
|
|
|
case MUSIC_STREAM_TYPE_NONE:
|
|
Dbg_MsgAssert(0, ("Bad start music stream state"));
|
|
break;
|
|
}
|
|
|
|
// No longer waiting
|
|
gMusicStreamWaitingToStart = false;
|
|
}
|
|
#endif // WAIT_AFTER_STOP_STREAM
|
|
|
|
static void sSetCorrectMusicVolume()
|
|
{
|
|
#ifdef __PLAT_XBOX__
|
|
if (s_xbox_play_user_soundtracks)
|
|
{
|
|
// This 'if' is part of the fix to TT6957, where user soundtracks were getting sfx vol
|
|
// when music vol turned down to zero.
|
|
PCMAudio_SetMusicVolume( Pcm::GetVolume() );
|
|
}
|
|
else if (gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC)
|
|
{
|
|
Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
|
|
PCMAudio_SetMusicVolume( sfx_manager->GetMainVolume() );
|
|
}
|
|
#else
|
|
// Ken: I added this as a fix to TT5588, 'ambient tracks don't play when music vol is set to zero'
|
|
// This forces the ambient track to use the sfx volume rather than gMusicVolume.
|
|
// Hopefully there is not too much of an overhead to calling PCMAudio_SetMusicVolume ...
|
|
// This bit of code won't be executed every frame though, but every SONG_UPDATE_INTERVAL frames,
|
|
// so I think it'll be OK ...
|
|
if (gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC)
|
|
{
|
|
Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
|
|
//printf("Playing ambient track: Forcing volume to %f ...\n",sfx_manager->GetMainVolume());
|
|
PCMAudio_SetMusicVolume( sfx_manager->GetMainVolume() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void RandomTrackUpdate( void )
|
|
{
|
|
|
|
TrackList *pTrackList = &gTrackLists[ gCurrentTrackList ];
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
|
|
if ( pTrackList->allTracksForbidden )
|
|
{
|
|
// Dbg_Message( "all tracks forbidden.. random mode" );
|
|
return;
|
|
}
|
|
int pcmStatus = PCMAudio_GetMusicStatus( );
|
|
if ( pcmStatus == PCM_STATUS_FREE )
|
|
{
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
// If we were waiting for StopMusic() to finish, we need to start the music stream
|
|
if (gMusicStreamWaitingToStart)
|
|
{
|
|
_StartMusicStream();
|
|
return;
|
|
}
|
|
#endif // WAIT_AFTER_STOP_STREAM
|
|
|
|
gMusicStreamType = MUSIC_STREAM_TYPE_NONE; // Just in case we were playing a music stream
|
|
sSetCorrectMusicVolume();
|
|
|
|
// Replay track if looping
|
|
if (gMusicLooping)
|
|
{
|
|
PlayTrack( gTrackName, true );
|
|
return;
|
|
}
|
|
|
|
if ( !pTrackList->numTracks )
|
|
{
|
|
Dbg_MsgAssert( 0,( "AllTracksForbidden flag should be set if no tracks exist." ));
|
|
return;
|
|
}
|
|
|
|
if ( shuffle_random_songs )
|
|
{
|
|
// Remember the last song in the previous order, so we can make sure that
|
|
// the first song in the new order is different from it.
|
|
// Note: OK if the last song was just an uninitialised random value, won't cause any problems.
|
|
int last_song=sp_random_song_order[num_random_songs-1];
|
|
|
|
// count active tracks
|
|
num_random_songs=0;
|
|
for (int i=0; i<pTrackList->numTracks; ++i)
|
|
{
|
|
if ( i < 64)
|
|
{
|
|
if ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << i ) ) )
|
|
{
|
|
// Intialize order
|
|
sp_random_song_order[num_random_songs]=i;
|
|
// add one
|
|
num_random_songs++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !( pTrackList->trackForbidden1 & ( ((uint64)1) << (i-64) ) ) )
|
|
{
|
|
// Intialize order
|
|
sp_random_song_order[num_random_songs]=i;
|
|
// add one
|
|
num_random_songs++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// need to shuffle order
|
|
printf("shuffling random song order num_songs=%i\n", num_random_songs );
|
|
|
|
// Jumble it up.
|
|
for (int n=0; n<2000; ++n)
|
|
{
|
|
int a=Mth::Rnd(num_random_songs);
|
|
int b=Mth::Rnd(num_random_songs);
|
|
|
|
if ( a != b )
|
|
{
|
|
int temp=sp_random_song_order[a];
|
|
sp_random_song_order[a]=sp_random_song_order[b];
|
|
sp_random_song_order[b]=temp;
|
|
}
|
|
}
|
|
|
|
// If the first song in the new order equals the last song of the last order,
|
|
// do one further swap to make sure it is different.
|
|
if (sp_random_song_order[0]==last_song)
|
|
{
|
|
// a is the first song.
|
|
int a=0;
|
|
|
|
// Choose b to be a random one of the other songs.
|
|
int b;
|
|
if (num_random_songs>1)
|
|
{
|
|
// Make b not able to equal 0, so that we don't swap the first with itself.
|
|
b=1+Mth::Rnd(num_random_songs-1);
|
|
}
|
|
else
|
|
{
|
|
// Unless there is only one song, in which case just swap it with itself. No point but what the heck.
|
|
b=0;
|
|
}
|
|
|
|
// Do the swap.
|
|
int temp=sp_random_song_order[a];
|
|
sp_random_song_order[a]=sp_random_song_order[b];
|
|
sp_random_song_order[b]=temp;
|
|
}
|
|
|
|
// reset index
|
|
random_song_index=0;
|
|
shuffle_random_songs=false;
|
|
pTrackList->trackPlayed0 = 0;
|
|
pTrackList->trackPlayed1 = 0;
|
|
}
|
|
else
|
|
{
|
|
random_song_index++;
|
|
if ( random_song_index >= (num_random_songs-1) )
|
|
{
|
|
shuffle_random_songs=true;
|
|
}
|
|
}
|
|
|
|
for ( int i = 0; i < pTrackList->numTracks; i++ )
|
|
{
|
|
int t = ( sp_random_song_order[random_song_index] );
|
|
//printf("index = %i song = %i\n", random_song_index, sp_random_song_order[random_song_index] );
|
|
|
|
if ( ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << t ) ) && ( t < 64 ) &&
|
|
!( pTrackList->trackPlayed0 & ( ((uint64)1) << t ) ) ) )
|
|
{
|
|
pTrackList->trackPlayed0 |= ( ((uint64)1) << t );
|
|
PlayTrack( pTrackList->trackInfo[ t ].trackName );
|
|
current_music_track=t;
|
|
Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ t ].trackName, t, current_music_track );
|
|
|
|
// update track text on screen
|
|
Script::CStruct *pParams = new Script::CStruct;
|
|
pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
|
|
Script::RunScript( "spawn_update_music_track_text", pParams );
|
|
delete pParams;
|
|
|
|
return;
|
|
}
|
|
|
|
if ( ( !( pTrackList->trackForbidden1 & ( ((uint64)1) << (t-64) ) ) && ( t >= 64 ) &&
|
|
!( pTrackList->trackPlayed1 & ( ((uint64)1) << (t-64) ) ) )
|
|
)
|
|
{
|
|
pTrackList->trackPlayed1 |= ( ((uint64)1) << (t-64) );
|
|
PlayTrack( pTrackList->trackInfo[ t ].trackName );
|
|
current_music_track=t;
|
|
Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ t ].trackName, t, current_music_track );
|
|
|
|
// update track text on screen
|
|
Script::CStruct *pParams = new Script::CStruct;
|
|
pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
|
|
Script::RunScript( "spawn_update_music_track_text", pParams );
|
|
delete pParams;
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackUpdate( void )
|
|
{
|
|
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
int trackList = gCurrentTrackList;
|
|
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
|
|
{
|
|
trackList = TRACKLIST_LEVEL_SPECIFIC;
|
|
}
|
|
|
|
TrackList *pTrackList = &gTrackLists[ trackList ];
|
|
|
|
if ( pTrackList->allTracksForbidden )
|
|
{
|
|
// Dbg_Message( "all tracks forbidden.. sequential mode" );
|
|
return;
|
|
}
|
|
int pcmStatus = PCMAudio_GetMusicStatus( );
|
|
if ( pcmStatus == PCM_STATUS_FREE )
|
|
{
|
|
#if WAIT_AFTER_STOP_STREAM
|
|
// If we were waiting for StopMusic() to finish, we need to start the music stream
|
|
if (gMusicStreamWaitingToStart)
|
|
{
|
|
_StartMusicStream();
|
|
return;
|
|
}
|
|
#endif // WAIT_AFTER_STOP_STREAM
|
|
|
|
gMusicStreamType = MUSIC_STREAM_TYPE_NONE; // Just in case we were playing a music stream
|
|
sSetCorrectMusicVolume();
|
|
|
|
// Replay track if looping
|
|
if (gMusicLooping)
|
|
{
|
|
PlayTrack( gTrackName, true );
|
|
return;
|
|
}
|
|
|
|
// don't have a random function. just play next track...
|
|
int i;
|
|
for ( i = 0; i < pTrackList->numTracks; i++ )
|
|
{
|
|
if ( ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << i ) ) && ( i < 64 ) &&
|
|
!( pTrackList->trackPlayed0 & ( ((uint64)1) << i ) ) ) )
|
|
{
|
|
pTrackList->trackPlayed0 |= ( ((uint64)1) << i );
|
|
PlayTrack( pTrackList->trackInfo[ i ].trackName );
|
|
current_music_track=i;
|
|
Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ i ].trackName, i, current_music_track );
|
|
|
|
// update track text on screen
|
|
Script::CStruct *pParams = new Script::CStruct;
|
|
pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
|
|
Script::RunScript( "spawn_update_music_track_text", pParams );
|
|
delete pParams;
|
|
|
|
return;
|
|
}
|
|
|
|
if ( ( !( pTrackList->trackForbidden1 & ( ((uint64)1) << (i-64) ) ) && ( i >= 64 ) &&
|
|
!( pTrackList->trackPlayed1 & ( ((uint64)1) << (i-64) ) ) ) )
|
|
{
|
|
pTrackList->trackPlayed1 |= ( ((uint64)1) << (i-64) );
|
|
PlayTrack( pTrackList->trackInfo[ i ].trackName );
|
|
current_music_track=i;
|
|
Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ i ].trackName, i, current_music_track );
|
|
|
|
// update track text on screen
|
|
Script::CStruct *pParams = new Script::CStruct;
|
|
pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
|
|
Script::RunScript( "spawn_update_music_track_text", pParams );
|
|
delete pParams;
|
|
|
|
return;
|
|
}
|
|
}
|
|
// all the tracks have been played... reset:
|
|
pTrackList->trackPlayed0 = 0;
|
|
pTrackList->trackPlayed1 = 0;
|
|
}
|
|
}
|
|
|
|
//powers of 2 please...
|
|
#define SONG_UPDATE_INTERVAL 64
|
|
#define STREAM_POSITIONAL_UPDATE_INTERVAL 4
|
|
|
|
// Call this function every frame...
|
|
// keeps tracks playing during levels and shit like that...
|
|
void Update( void )
|
|
{
|
|
|
|
//static int streamingObjToUpdate = 0;
|
|
|
|
if (NoMusicPlease() && StreamsDisabled()) return;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::Update when PCM Audio not initialized." ));
|
|
|
|
int err_no = PCMAudio_Update( );
|
|
if ( err_no != 0 )
|
|
{
|
|
if (err_no < 0)
|
|
{
|
|
StopStreams( );
|
|
StopMusic( );
|
|
} else {
|
|
// Even though we are returning the stream number, lets just kill them all because we are at the end of the project
|
|
StopStreams( );
|
|
}
|
|
}
|
|
|
|
// Garrett: This should go away, since the IOP communication is async now. But I left it in for
|
|
// the music now since I don't know if there is a CPU hit there.
|
|
sCounter++;
|
|
|
|
//if ( !( sCounter & ( STREAM_POSITIONAL_UPDATE_INTERVAL - 1 ) ) )
|
|
for (int streamingObjToUpdate = 0; streamingObjToUpdate < NUM_STREAMS; streamingObjToUpdate++)
|
|
{
|
|
if ( gpStreamingObj[ streamingObjToUpdate ] )
|
|
{
|
|
if ( !SetStreamVolume( streamingObjToUpdate ) )
|
|
{
|
|
gpStreamingObj[ streamingObjToUpdate ]->mStreamingID[ streamingObjToUpdate ] = 0;
|
|
gpStreamingObj[ streamingObjToUpdate ] = NULL;
|
|
}
|
|
}
|
|
//// update the other one next time...
|
|
//streamingObjToUpdate++;
|
|
//if ( streamingObjToUpdate >= NUM_STREAMS )
|
|
//{
|
|
// streamingObjToUpdate = 0;
|
|
//}
|
|
}
|
|
|
|
// Free any frame amp data that was used by a stream that already stopped
|
|
for (int i = 0; i < NUM_STREAMS; i++)
|
|
{
|
|
if (gCurrentStreamInfo[i].p_frame_amp && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[i].voice) == PCM_STATUS_FREE))
|
|
{
|
|
Dbg_Message("Clearing StreamFrameAmp %s", Script::FindChecksumName(gCurrentStreamInfo[i].controlID));
|
|
CStreamFrameAmpManager::sFreeFrameAmp(gCurrentStreamInfo[i].p_frame_amp);
|
|
gCurrentStreamInfo[i].p_frame_amp = NULL;
|
|
}
|
|
}
|
|
|
|
if ( sCounter & ( SONG_UPDATE_INTERVAL - 1 ) )
|
|
return;
|
|
|
|
// if volume is turned all the way down, the current song will finish
|
|
// playing and then we'll keep returning here until volume isn't zero,
|
|
// as soon as it is turned up a new song will begin playing.
|
|
if ( gMusicVolume == 0.0f && gCurrentTrackList != TRACKLIST_LEVEL_SPECIFIC)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef __PLAT_XBOX__
|
|
// This is part of the fix to TT6957,
|
|
// where user soundtracks were getting sfx vol when music vol turned down to zero.
|
|
// (The other part of the fix is a bit further down, search for TT6957)
|
|
if ( gMusicVolume == 0.0f && s_xbox_play_user_soundtracks)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (sMusicIsPaused)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// K: Commented this out since it does not appear to be needed, and was stopping random
|
|
// track mode from working on XBox & Gamecube, I think because is_frontend is defined to
|
|
// be 1 in mode_skateshop in gamemode.q. I commented this out instead of changing
|
|
// is_frontend to 0 in case that broke something else.
|
|
|
|
// don't play music in the frontend: <- old comment
|
|
/*
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
|
|
{
|
|
TrackUpdate( );
|
|
return;
|
|
}
|
|
*/
|
|
|
|
|
|
// if song is finished, start a new one playing...
|
|
if ( gMusicInRandomMode || ( gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC ) )
|
|
RandomTrackUpdate( );
|
|
else
|
|
TrackUpdate( );
|
|
}
|
|
|
|
void GetPlaylist( uint64* flags1, uint64* flags2 )
|
|
{
|
|
*flags1 = gTrackLists[ TRACKLIST_PERM ].trackForbidden0;
|
|
*flags2 = gTrackLists[ TRACKLIST_PERM ].trackForbidden1;
|
|
}
|
|
|
|
void SetPlaylist( uint64 flags1, uint64 flags2 )
|
|
{
|
|
gTrackLists[ TRACKLIST_PERM ].trackForbidden0 = flags1;
|
|
gTrackLists[ TRACKLIST_PERM ].trackForbidden1 = flags2;
|
|
}
|
|
|
|
// Ambient volume is the same as SoundFX volume...
|
|
void SetAmbientVolume( float volPercent )
|
|
{
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
|
|
{
|
|
return;
|
|
}
|
|
if ( gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC )
|
|
{
|
|
PCMAudio_SetMusicVolume( volPercent );
|
|
}
|
|
}
|
|
|
|
// set a particular song as 'forbidden' (don't play it, the player hates that song...)
|
|
void SetTrackForbiddenStatus( int trackNum, bool forbidden, int whichList )
|
|
{
|
|
if (NoMusicPlease()) return;
|
|
|
|
shuffle_random_songs=true;
|
|
|
|
int i;
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
|
|
TrackList *pTrackList = &gTrackLists[ whichList ];
|
|
Dbg_MsgAssert( trackNum < pTrackList->numTracks,( "Not that many tracks in list." ));
|
|
if ( forbidden )
|
|
{
|
|
if ( trackNum < 64)
|
|
{
|
|
pTrackList->trackForbidden0 |= ( ((uint64)1) << trackNum );
|
|
}
|
|
else
|
|
{
|
|
pTrackList->trackForbidden1 |= ( ((uint64)1) << (trackNum-64) );
|
|
}
|
|
|
|
if ( TrackIsPlaying( whichList, trackNum ) )
|
|
{
|
|
StopMusic( );
|
|
}
|
|
for ( i = 0; i < pTrackList->numTracks; i++ )
|
|
{
|
|
if ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << i ) ) || !( pTrackList->trackForbidden1 & ( ((uint64)1) << (i-64) ) ) )
|
|
{
|
|
pTrackList->allTracksForbidden = false;
|
|
return;
|
|
}
|
|
}
|
|
pTrackList->allTracksForbidden = true;
|
|
return;
|
|
}
|
|
|
|
if ( trackNum < 64)
|
|
{
|
|
pTrackList->trackForbidden0 &= ~( ((uint64)1) << trackNum );
|
|
}
|
|
else
|
|
{
|
|
pTrackList->trackForbidden1 &= ~( ((uint64)1) << (trackNum-64) );
|
|
}
|
|
pTrackList->allTracksForbidden = false;
|
|
}
|
|
|
|
/*void CheckLockedTracks()
|
|
{
|
|
if ( !Mdl::Skate::Instance()->GetCareer()->GetGlobalFlag(286) )
|
|
{
|
|
// if the kiss songs aren't unlocked make sure they stay forbidden
|
|
SetTrackForbiddenStatus( 32, true, Pcm::TRACKLIST_PERM );
|
|
SetTrackForbiddenStatus( 33, true, Pcm::TRACKLIST_PERM );
|
|
}
|
|
}*/
|
|
|
|
bool TrackIsPlaying( int whichList, int whichTrack )
|
|
{
|
|
|
|
if (NoMusicPlease()) return false;
|
|
|
|
Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm func when PCM Audio not initialized." ));
|
|
int pcmStatus = PCMAudio_GetMusicStatus( );
|
|
if ( pcmStatus == PCM_STATUS_FREE )
|
|
return ( false );
|
|
if ( whichTrack >= gTrackLists[ whichList ].numTracks )
|
|
return ( false );
|
|
// can't do a strcmp, because files in gTrackName have been converted to all uppercase...
|
|
if ( Script::GenerateCRC( gTrackName ) == Script::GenerateCRC( gTrackLists[ whichList ].trackInfo[ whichTrack ].trackName ) )
|
|
return ( true );
|
|
return ( false );
|
|
}
|
|
|
|
int GetNumTracks( int whichList )
|
|
{
|
|
|
|
|
|
if (NoMusicPlease()) return 0;
|
|
|
|
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
|
|
TrackList *pTrackList = &gTrackLists[ whichList ];
|
|
return ( pTrackList->numTracks );
|
|
}
|
|
|
|
int GetTrackForbiddenStatus( int trackNum, int whichList )
|
|
{
|
|
|
|
|
|
if (NoMusicPlease()) return 1;
|
|
|
|
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
|
|
TrackList *pTrackList = &gTrackLists[ whichList ];
|
|
Dbg_MsgAssert( trackNum < pTrackList->numTracks,( "Requesting forbidden status on invalid track." ));
|
|
|
|
if ( trackNum < 64 )
|
|
{
|
|
if (pTrackList->trackForbidden0 & ( ((uint64)1) << trackNum ))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pTrackList->trackForbidden1 & ( ((uint64)1) << (trackNum-64) ))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *GetTrackName( int trackNum, int whichList )
|
|
{
|
|
|
|
|
|
if (NoMusicPlease())
|
|
{
|
|
strcpy( gTrackLists[ 0 ].trackInfo[ 0 ].trackName, "Empty." );
|
|
return ( gTrackLists[ 0 ].trackInfo[ 0 ].trackName );
|
|
}
|
|
|
|
//if ( whichList == TRACKLIST_PERM )
|
|
//{
|
|
// Dbg_MsgAssert( trackNum < MAX_NUM_TRACKS, ( "Invalid track num specified." ) );
|
|
// return ( PermTrackTitle[ trackNum ].trackTitle );
|
|
//}
|
|
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
|
|
TrackList *pTrackList = &gTrackLists[ whichList ];
|
|
Dbg_MsgAssert( trackNum < pTrackList->numTracks,( "Requesting invalid track name." ));
|
|
int i;
|
|
char *pText = pTrackList->trackInfo[ trackNum ].trackName;
|
|
char *pRet = pText;
|
|
int len = strlen( pText );
|
|
for ( i = 0; i < len; i++ )
|
|
if ( ( pText[ i ] == '\\' ) || ( pText[ i ] == '/' ) )
|
|
pRet = &pText[ i + 1 ];
|
|
return ( pRet );
|
|
}
|
|
|
|
// Ambient (TRACKLIST_LEVEL_SPECIFIC) or music (TRACKLIST_PERM)
|
|
void SetActiveTrackList( int whichList )
|
|
{
|
|
|
|
|
|
if (NoMusicPlease()) return;
|
|
|
|
Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
|
|
if ( gCurrentTrackList != whichList )
|
|
{
|
|
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
|
|
if ( !skate_mod->GetGameMode( )->IsFrontEnd( ) )
|
|
{
|
|
StopMusic( );
|
|
}
|
|
}
|
|
gCurrentTrackList = whichList;
|
|
}
|
|
|
|
// A user can choose to listen to music or ambience during gameplay.
|
|
int GetActiveTrackList( void )
|
|
{
|
|
|
|
return ( gCurrentTrackList );
|
|
}
|
|
|
|
// A music header contains the names and sizes of all the music files available:
|
|
bool LoadMusicHeader( const char *nameOfFile )
|
|
{
|
|
if (NoMusicPlease()) return false;
|
|
|
|
music_hed_there =( PCMAudio_LoadMusicHeader( nameOfFile ) );
|
|
|
|
return music_hed_there;
|
|
}
|
|
|
|
// Headers contain all available streams (probably will be one header per level).
|
|
bool LoadStreamHeader( const char *nameOfFile )
|
|
{
|
|
if (StreamsDisabled()) return false;
|
|
|
|
streams_hed_there = PCMAudio_LoadStreamHeader( nameOfFile );
|
|
|
|
return streams_hed_there;
|
|
}
|
|
|
|
int GetNumStreamsAvailable( void )
|
|
{
|
|
return ( NUM_STREAMS );
|
|
}
|
|
|
|
bool StreamAvailable( int whichStream )
|
|
{
|
|
if (StreamsDisabled()) return false;
|
|
|
|
if ( PCMAudio_GetStreamStatus( whichStream ) == PCM_STATUS_FREE )
|
|
{
|
|
return ( true );
|
|
}
|
|
return ( false );
|
|
}
|
|
|
|
bool StreamAvailable( void )
|
|
{
|
|
|
|
if (StreamsDisabled()) return false;
|
|
|
|
if (!(Config::CD() || TEST_FROM_CD))
|
|
{
|
|
int testStreams = 0;
|
|
testStreams = Script::GetInt( 0x62df9442, false ); // checksum 'testStreamsFromHost'
|
|
if ( !testStreams )
|
|
{
|
|
return ( true );
|
|
}
|
|
}
|
|
|
|
// return ( PCMAudio_GetStreamStatus( 0 ) == PCM_STATUS_FREE );
|
|
|
|
int i;
|
|
for ( i = 0; i < NUM_STREAMS; i++ )
|
|
{
|
|
if ( PCMAudio_GetStreamStatus( i ) == PCM_STATUS_FREE )
|
|
{
|
|
return ( true );
|
|
}
|
|
}
|
|
return ( false );
|
|
}
|
|
|
|
bool StreamExists( uint32 streamChecksum )
|
|
{
|
|
// try to find the name in the header file using the checksum:
|
|
uint32 streamName = PCMAudio_FindNameFromChecksum( streamChecksum, EXTRA_CHANNEL );
|
|
return ( streamName != NULL );
|
|
}
|
|
|
|
bool StreamLoading( uint32 streamID )
|
|
{
|
|
if (StreamsDisabled()) return false;
|
|
|
|
if (!(Config::CD() || TEST_FROM_CD))
|
|
{
|
|
int testStreams = 0;
|
|
testStreams = Script::GetInt( 0x62df9442, false ); // checksum 'testStreamsFromHost'
|
|
if ( !testStreams )
|
|
{
|
|
return ( false );
|
|
}
|
|
}
|
|
|
|
int channel = GetChannelFromID(streamID);
|
|
|
|
if ( channel >= 0 )
|
|
{
|
|
return ( PCMAudio_GetStreamStatus( channel ) == PCM_STATUS_LOADING );
|
|
}
|
|
|
|
return ( false );
|
|
}
|
|
|
|
bool StreamPlaying( uint32 streamID )
|
|
{
|
|
if (StreamsDisabled()) return false;
|
|
|
|
int channel = GetChannelFromID(streamID, false);
|
|
|
|
if ( channel >= 0 )
|
|
{
|
|
return true; // Even if it is in loading state
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int GetCurrentTrack()
|
|
{
|
|
return ( current_music_track );
|
|
}
|
|
|
|
void SetCurrentTrack( int value )
|
|
{
|
|
current_music_track=value;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Pcm::CStreamFrameAmp
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
CStreamFrameAmp::CStreamFrameAmp()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
CStreamFrameAmp::~CStreamFrameAmp()
|
|
{
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void CStreamFrameAmp::Init()
|
|
{
|
|
m_checksum = vNOT_ALLOCATED;
|
|
m_num_samples = 0;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
uint8 CStreamFrameAmp::GetSample(int frame) const
|
|
{
|
|
Dbg_Assert(is_allocated());
|
|
Dbg_Assert(frame < m_num_samples);
|
|
|
|
return m_frame_amp_samples[frame];
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
const uint8 * CStreamFrameAmp::GetSamples() const
|
|
{
|
|
Dbg_Assert(is_allocated());
|
|
|
|
return m_frame_amp_samples;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
const uint8 * CStreamFrameAmp::GetSamples(int frame) const
|
|
{
|
|
Dbg_Assert(is_allocated());
|
|
Dbg_Assert(frame < m_num_samples);
|
|
|
|
return &m_frame_amp_samples[frame];
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
int CStreamFrameAmp::GetNumSamples() const
|
|
{
|
|
return m_num_samples;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool CStreamFrameAmp::load(uint32 stream_checksum)
|
|
{
|
|
File::CAsyncFileHandle *p_handle;
|
|
char filename[256];
|
|
|
|
// Generate filename from checksum
|
|
sprintf(filename,"fam\\%x.fam", stream_checksum);
|
|
|
|
// Open with async blocking for now. Look to make truly async later.
|
|
p_handle = File::CAsyncFileLoader::sOpen(filename, true);
|
|
if (p_handle)
|
|
{
|
|
m_num_samples = p_handle->GetFileSize();
|
|
if (m_num_samples >= vMAX_SAMPLES)
|
|
{
|
|
Dbg_MsgAssert(0, (".fam file has too many samples: %d", m_num_samples));
|
|
File::CAsyncFileLoader::sClose(p_handle);
|
|
return false;
|
|
}
|
|
|
|
p_handle->Read(m_frame_amp_samples, 1, m_num_samples);
|
|
File::CAsyncFileLoader::sClose(p_handle);
|
|
|
|
m_checksum = stream_checksum;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool CStreamFrameAmp::free()
|
|
{
|
|
if (is_allocated())
|
|
{
|
|
// Easiest way to clear, since there is nothing to de-allocate.
|
|
Init();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Pcm::CStreamFrameAmpManager
|
|
|
|
CStreamFrameAmp CStreamFrameAmpManager::s_frame_amp_data[vMAX_BUFFERS];
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
CStreamFrameAmp * CStreamFrameAmpManager::sLoadFrameAmp(uint32 stream_checksum)
|
|
{
|
|
for (int i = 0; i < vMAX_BUFFERS; i++)
|
|
{
|
|
if (!s_frame_amp_data[i].is_allocated())
|
|
{
|
|
if (s_frame_amp_data[i].load(stream_checksum))
|
|
{
|
|
return &s_frame_amp_data[i];
|
|
}
|
|
else
|
|
{
|
|
// If we got here, there weren't any free slots
|
|
Dbg_MsgAssert(0, ("Couldn't load StreamFrameAmp %s", Script::FindChecksumName(stream_checksum)));
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we got here, there weren't any free slots
|
|
Dbg_MsgAssert(0, ("Can't load StreamFrameAmp %s: no free slots", Script::FindChecksumName(stream_checksum)));
|
|
return NULL;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
CStreamFrameAmp * CStreamFrameAmpManager::sGetFrameAmp(uint32 stream_checksum)
|
|
{
|
|
for (int i = 0; i < vMAX_BUFFERS; i++)
|
|
{
|
|
if (s_frame_amp_data[i].GetStreamNameCRC() == stream_checksum)
|
|
{
|
|
return &s_frame_amp_data[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool CStreamFrameAmpManager::sFreeFrameAmp(uint32 stream_checksum)
|
|
{
|
|
for (int i = 0; i < vMAX_BUFFERS; i++)
|
|
{
|
|
if (s_frame_amp_data[i].GetStreamNameCRC() == stream_checksum)
|
|
{
|
|
return s_frame_amp_data[i].free();
|
|
}
|
|
}
|
|
|
|
// If we got here, we didn't find it
|
|
Dbg_MsgAssert(0, ("Can't find StreamFrameAmp %s data to free", Script::FindChecksumName(stream_checksum)));
|
|
return false;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool CStreamFrameAmpManager::sFreeFrameAmp(CStreamFrameAmp *p_fam_data)
|
|
{
|
|
return p_fam_data->free();
|
|
}
|
|
|
|
} // namespace Pcm
|