/*********************************************************************************************** * * Copyright © DreamWorks Interactive. 1997 * * Contents: * CAudioDaemon implementation * * Bugs: * * To do: * * Add response messages for all AudioMessages * May need to listen for delete messages so object attachments can be removed * Global audio memory management so we do not exceed our memory limit. * Deferred loading of all sample types, this required CSample and CAudio to be modifed * *********************************************************************************************** * * $Log:: /JP2_PC/Source/Lib/Audio/AudioDaemon.cpp $ * * 135 10/03/98 11:13p Pkeet * Added safety check. * * 134 10/02/98 3:52a Rwyatt * Ambients are now removed on clean up. * * 133 10/02/98 2:35a Agrant * don't defer collisions * * 132 10/01/98 6:49p Mlange * Improved collision message stats. * * 131 10/01/98 4:05p Mlange * Improved move message stat reporting. * * 130 9/30/98 5:59p Rwyatt * Sound effects now can either defer load or not * * 129 9/29/98 4:36p Rwyatt * Fixed sound effect problem when hardware was enabled. * * 128 9/29/98 1:04a Rwyatt * Ambient samples are only saved if they have a valid attachment. * All attachment timimgs are done eith real time and not elapsed time * ***********************************************************************************************/ // AudioDaemon is the exception and it includes common.hpp and audio.hpp #include "common.hpp" #include "AudioDaemon.hpp" #include "Lib/Audio/Audio.hpp" #include "Lib/GeomDBase/PartitionPriv.hpp" #include "Lib/EntityDBase/Container.hpp" #include "Lib/EntityDBase/Entity.hpp" #include "Lib/EntityDBase/Subsystem.hpp" #include "Lib/EntityDBase/MessageTypes/MsgStep.hpp" #include "Lib/EntityDBase/MessageTypes/MsgAudio.hpp" #include "Lib/EntityDBase/MessageTypes/MsgMove.hpp" #include "Lib/EntityDBase/MessageTypes/MsgCollision.hpp" #include "Lib/EntityDBase/MessageTypes/MsgPaint.hpp" #include "Lib/EntityDBase/MessageTypes/MsgSystem.hpp" #include "Lib/EntityDBase/Query/QRenderer.hpp" #include "Lib/EntityDBase/TextOverlay.hpp" #include "Lib/Renderer/Camera.hpp" #include "Game/DesignDaemon/Player.hpp" #include "Lib/Groff/EasyString.hpp" #include "Lib/Loader/SaveFile.hpp" #include "Game/AI/AIMain.hpp" #include "lib/sys/memorylog.hpp" #include "Lib/Sys/FileEx.hpp" #include "Lib/Sys/Profile.hpp" #include "Lib/View/RasterVid.hpp" //********************************************************************************************* // Define this to dump out all audio information //#define AUMSG(x) dprintf(x); #define AUMSG(x) //********************************************************************************************* #define fVOICEOVER_EXTEND (1.0f) + fSTREAM_LENGTH; //********************************************************************************************* // The sample extend bit is defined as user bit 1 #define AU_ATTACHED AU_USER_BIT1 //********************************************************************************************* // define DUMP_COLLISIONS to show object collisions // #define DUMP_COLLISIONS //********************************************************************************************* // Global Audio Daemon pointer, initialized at the very beginning of setup along with the // audio system. // CAudioDaemon* padAudioDaemon = 0; static CSample* psamTest = 0; //********************************************************************************************* // Static settings that will survive deleting the audio daemon and recreating it. // bool CAudioDaemon::bTerrainSound = true; // all features are enabled by default... uint32 CAudioDaemon::u4FeaturesEnabled = AUDIO_FEATURE_VOICEOVER | AUDIO_FEATURE_EFFECT | AUDIO_FEATURE_MUSIC | AUDIO_FEATURE_AMBIENT | AUDIO_FEATURE_SUBTITLES; char CAudioDaemon::str_DataPath[_MAX_PATH] = {"\0"}; //********************************************************************************************* CAudioDaemon::CAudioDaemon() { uint32 u4_sample_count; SetInstanceName("Audio Daemon"); MEMLOG_ADD_COUNTER(emlSoundControl,sizeof(CAudioDaemon)); bVoiceOver = false; // there is no active voice over at startup bMusic = false; bDelayedVoiceOver = false; bExit = false; msgAudioVoiceOver.sndhndSample = 0; bAmbientMuted = false; sAmbientRestore = 0.0f; sndhndVoiceOver = 0; sndhndMusic = 0; // clear out the delayed voice over lists for (u4_sample_count = 0; u4_sample_countCleanUp(); if (bMusic) { RemoveSoundAttachment(psamMusic); delete psamMusic; bMusic = false; } if (bVoiceOver) { RemoveSoundAttachment(psamVoiceOver); delete psamVoiceOver; bVoiceOver = false; } // delete any delayed voiceovers bDelayedVoiceOver = false; for (u4_sample_count = 0; u4_sample_countbStopFlagged()) { // it is time to remove our last voice sample AUMSG("Stop voice over\n"); Assert(psamVoiceOver); // If this was a 3D voice over (of any type) then it must have been attached to an // instance. You cannot have an un-attached positional voice over. // Remove the attachment before we delete the sample class in case the object is moving. // This could create a fault if the move message is somehow processed if (psamVoiceOver->u4CreateFlags & (AU_CREATE_PSEUDO_3D|AU_CREATE_SPATIAL_3D)) { RemoveSoundAttachment(psamVoiceOver); } // if there another voice over waiting for us?? if (bDelayedVoiceOver) { // delete the old voice over which will tidy up the stream if it was streamed delete psamVoiceOver; psamVoiceOver = NULL; bool b_find = false; // we have to find the delayed voice over and play it... for (uint32 u4=0; u4bPlay(AU_PLAY_ONCE)) { //sVoiceFinish = msg_step.sElapsedRealTime+psamVoiceOver->fSamplePlayTime()+fVOICEOVER_EXTEND; break; } else { // if we have failed, delete it and try the next. // This may upset the subtitles if there is another delayed voice over, dprintf("Delayed voiceover failed to start\n"); delete psamVoiceOver; continue; } } } // we have not found a voice over in the delay list so we have done if (!b_find) { bDelayedVoiceOver = false; } } else { // delete the old voice over which will tidy up the stream if it was streamed delete psamVoiceOver; psamVoiceOver = NULL; // no delayed voice over so disable voice overs bVoiceOver = false; bDelayedVoiceOver = false; } // if we have stopeed playing a voice over and there is music playing // then puts its volume back to normal... if (!bVoiceOver) { if (bMusic) { float f_mvol = psamMusic->fGetVolume() + MUSIC_VOLUME_ADJUST; if (f_mvol>0.0f) f_mvol = 0.0f; psamMusic->SetVolume(f_mvol); } } } } if (bMusic) { // // Is the sample marked as finished streaming? // if ( psamMusic->bStopFlagged() ) { // it is time to remove our last voice sample AUMSG("Stop music stream\n"); Assert(psamMusic); // remove from the fade list just in case it is fading CAudio::pcaAudio->StopFade(psamMusic); delete psamMusic; // If this was a 3D music stream (of any type) then it must have been attached to an // instance. if (psamMusic->u4CreateFlags & (AU_CREATE_PSEUDO_3D|AU_CREATE_SPATIAL_3D)) { RemoveSoundAttachment(psamMusic); } bMusic = false; psamMusic = NULL; } } /* if (GetAsyncKeyState('G')<0) { CMessageAudio msg ( sndhndHashIdentifier("GUN - MAC10"), eamPOSITIONED_EFFECT, // type of sound NULL, // sender (NULL if using play function) padAudioDaemon, // receiver (NULL if using play function) NULL, // parent of the effect (can be NULL for stereo) 0.0f, // volume 0dBs 1.0f, // attenuation (only for pseudo/real 3D samples) AU_SPATIAL3D, // spatial type 360.0f, // fustrum angle (real 3D only) -15.0f, // outside volume (real 3D only) true, // looped 0, // loop count 10000.0f, // distance before sound is stopped (pseudo/real 3D only) 0, 0 ); ptr pcam = CWDbQueryActiveCamera().tGet(); msg.SetDeferLoad(false); msg.SetPositionalInfo ( pcam->pr3Presence().v3Pos ); bImmediateSoundEffect(msg,NULL); }*/ /* if (GetAsyncKeyState('G')<0) { CMessageAudio msg ( 0x8d6b6c73, eamVOICEOVER, // type of sound NULL, // sender (NULL if using play function) padAudioDaemon, // receiver (NULL if using play function) NULL, // parent of the effect (can be NULL for stereo) 0.0f, // volume 0dBs 1.0f, // attenuation (only for pseudo/real 3D samples) AU_STEREO, // spatial type 360.0f, // fustrum angle (real 3D only) -15.0f, // outside volume (real 3D only) false, // looped 0, // loop count 10000.0f, // distance before sound is stopped (pseudo/real 3D only) 0, 0 ); msg.Dispatch(); }*/ // check if any looped effects need to be stopped ProcessLoopedCollisions(); // check if any attachments have timed out ProcessAttachments(); // Is it time to restore the ambient sounds if ((bAmbientMuted) && (CMessageStep::sStaticTotal > sAmbientRestore)) { RestoreAmbientSounds(); } // only commit the new 3D audio settings one per frmae to save a shit load of calculation // time. This is especially true when you move the listener because all samples need to // be recalculated. CAudio::pcaAudio->CommitSettings(); } //***************************************************************************************** // Process a system message. This is primarily used to start and stop the game. When the // game is stopped all playing audio should be paused. void CAudioDaemon::Process(const CMessageSystem& msg) { if (msg.escCode == escSTART_SIM) { //OutputDebugString("System sim start: Play audio.\n"); AUMSG("System sim start: Play audio.\n"); RestartPausedAudio(); CAudio::pcaAudio->ResumeAudioLoader(); } else if (msg.escCode == escSTOP_SIM) { //OutputDebugString("System sim stop: Pause audio.\n"); AUMSG("System stop sim: Pause audio.\n"); PauseAllPlayingAudio(); CAudio::pcaAudio->SuspendAudioLoader(); } } //***************************************************************************************** void CAudioDaemon::SetFeatures(uint32 u4_mask, uint32 u4_set) { u4FeaturesEnabled &= ~u4_mask; // clear the bits in the mask u4FeaturesEnabled |= u4_set; // set the bits in the feature set if ((u4FeaturesEnabled & AUDIO_FEATURE_VOICEOVER)==0) { // Switch off voice overs KillVoiceovers(); } if ((u4FeaturesEnabled & AUDIO_FEATURE_MUSIC)==0) { // Switch off music if it is playing KillMusic(); } if ((u4FeaturesEnabled & AUDIO_FEATURE_AMBIENT)==0) { // Switch off any ambients KillAmbientSamples(); } if ((u4FeaturesEnabled & AUDIO_FEATURE_SUBTITLES)==0) { // Switch off sub titles RemoveSubtitle(); } } //***************************************************************************************** char *CAudioDaemon::pcSaveSample ( char* pc, TSoundHandle sndhnd, CSample* psam ) const { uint32 u4_type; CInstance* pins = NULL; // save the handle of this sound pc = pcSaveT(pc, sndhnd); switch (psam->u4CreateFlags & AU_CREATE_TYPE_MASK) { case AU_CREATE_STEREO: u4_type = AU_STEREO; break; case AU_CREATE_SPATIAL_3D: u4_type = AU_SPATIAL3D; break; case AU_CREATE_PSEUDO_3D: u4_type = AU_PSEUDO3D; break; } // save the type of this sound pc = pcSaveT(pc, u4_type); // find what sample we are attached to. pins = pinsFindSampleAttachment(psam); #if VER_TEST if (u4_type == AU_PSEUDO3D) { if (pins == NULL) _asm int 3; } #endif // save the object this sound is attached to pc = pcSaveInstancePointer(pc, pins); // save all of the other sample properties pc = pcSaveT(pc, psam->fVolume); pc = pcSaveT(pc, psam->fAtten); pc = pcSaveT(pc, psam->fFustrumAngle); pc = pcSaveT(pc, psam->fOutsideVolume); pc = pcSaveT(pc, psam->fMaxVolDistance); pc = pcSaveT(pc, psam->fMasterVolume); return pc; } //***************************************************************************************** const char *CAudioDaemon::pcLoadSample ( const char* pc, CMessageAudio& msg ) { pc = pcLoadT(pc, &msg.sndhndSample); pc = pcLoadT(pc, &msg.u4SpatialType); pc = pcLoadInstancePointer(pc, &msg.pinsParent); pc = pcLoadT(pc, &msg.fVolume); pc = pcLoadT(pc, &msg.fAtten); pc = pcLoadT(pc, &msg.fFrustumAngle); pc = pcLoadT(pc, &msg.fOutsideVolume); pc = pcLoadT(pc, &msg.fMaxVolDistance); pc = pcLoadT(pc, &msg.fMasterVolume); return pc; } //***************************************************************************************** char *CAudioDaemon::pcSave ( char* pc ) const { // Version number. pc = pcSaveT(pc, 6); pc = pcSaveT(pc, CAudio::pcaAudio->u4GetFormat()); pc = pcSaveT(pc, CAudio::pcaAudio->esconGetSpeakerConfig()); pc = pcSaveT(pc, u4FeaturesEnabled); // // Now save what samples are playing // pc = pcSaveT(pc, bVoiceOver); pc = pcSaveT(pc, bMusic); if (bVoiceOver) { pc = pcSaveSample(pc, sndhndVoiceOver, psamVoiceOver); } if (bMusic) { pc = pcSaveSample(pc, sndhndMusic, psamMusic); } // // New for version 5 // // Save out the ambient mute state pc = pcSaveT(pc, bAmbientMuted); pc = pcSaveT(pc, sAmbientRestore); // Write out the number of ambient samples uint32 u4_samples = u4CountAmbientSounds(); pc = pcSaveT(pc, u4_samples); for (uint32 u4_sample_count = 0; u4_sample_count CMessageStep::sElapsedRealTime+amsndAmbients[u4_sample_count].psam->fPlayTime) { // we are looped pc = pcSaveT(pc, (bool)true); // looped // If the end time is huge this sample is looped continously if (amsndAmbients[u4_sample_count].sEndTime > CMessageStep::sElapsedRealTime+10000000.0f) { pc = pcSaveT(pc, (int32)-1); // loop count } else { float f_loop = (amsndAmbients[u4_sample_count].sEndTime - CMessageStep::sElapsedRealTime) / amsndAmbients[u4_sample_count].psam->fPlayTime; pc = pcSaveT(pc, (int32)(f_loop+0.5f)); // loop count } } else { // we are not looped pc = pcSaveT(pc, (bool)false); // looped pc = pcSaveT(pc, (int32)0); // loop count } pc = pcSaveT(pc, amsndAmbients[u4_sample_count].fMaxDistance); pc = pcSaveT(pc, amsndAmbients[u4_sample_count].u4Flags); pc = pcSaveT(pc, amsndAmbients[u4_sample_count].fRestoreVolume); u4_samples--; } } // Make sure we saved them all Assert(u4_samples == 0); return pc; } //***************************************************************************************** const char *CAudioDaemon::pcLoad ( const char* pc ) { int iVersion; CMessageAudio msg_voice(padAudioDaemon,NULL); CMessageAudio msg_music(padAudioDaemon,NULL); CMessageAudio* pmsg_amb = NULL; bool b_voice, b_music; uint32 u4_ambient_samples = 0; // Cleam out the audio system to its initial state. CleanUp(); pc = pcLoadT(pc, &iVersion); if ((iVersion >= 1) && (iVersion <= 6)) { uint32 u4_format; ESpeakerConfig escon; pc = pcLoadT(pc, &u4_format); pc = pcLoadT(pc, &escon); pc = pcLoadT(pc, &u4FeaturesEnabled); if (iVersion<6) { bool b_temp; uint32 u4_temp; pc = pcLoadT(pc, &b_temp); pc = pcLoadT(pc, &b_temp); pc = pcLoadT(pc, &u4_temp); } CAudio::pcaAudio->SetSpeakerConfig(escon); CAudio::pcaAudio->SetOutputFormat(u4_format & AU_FORMAT_FREQ, ((u4_format & AU_FORMAT_STEREO) ? escSTEREO : escMONO), ((u4_format & AU_FORMAT_16BIT) ? esb16BIT : esb8BIT) ); if (iVersion >= 3) { // Version 3 contains playing sample information pc = pcLoadT(pc, &b_voice); pc = pcLoadT(pc, &b_music); if (b_voice) { msg_voice.u4Flags = eamVOICEOVER; msg_voice.fMaxDistance = 100000.0f; msg_voice.bLooped = false; msg_voice.i4LoopCount = 0; msg_voice.sndhndLoopStop = 0; pc = pcLoadSample(pc,msg_voice); } if (b_music) { msg_music.u4Flags = eamMUSIC; msg_music.fMaxDistance = 100000.0f; msg_music.bLooped = false; msg_music.i4LoopCount = 0; msg_music.sndhndLoopStop = 0; pc = pcLoadSample(pc,msg_music); } } else { bVoiceOver = false; bMusic = false; } // // Version 4 is bad. // Version 5 is bad. // Version 6 is a working version of version 4 and 5 // AlwaysAssert(iVersion!=4); //AlwaysAssert(iVersion!=5); if (iVersion >= 6) { pc = pcLoadT(pc, &bAmbientMuted); pc = pcLoadT(pc, &sAmbientRestore); uint32 u4_count = 0; pc = pcLoadT(pc, &u4_ambient_samples); if (u4_ambient_samples>0) { pmsg_amb = new CMessageAudio[u4_ambient_samples]; memset(pmsg_amb,0,sizeof(CMessageAudio)*u4_ambient_samples); } while (u4_countReset(); if (padatStreams) padatStreams->Reset(); if (padatAmbient) padatAmbient->Reset(); // do we need to restore a voice over?? if (b_voice) { msg_voice.Dispatch(); } // Do we need to restore music?? if (b_music) { msg_music.Dispatch(); } // if we have a block of ambient samples, delete them if (pmsg_amb) { // Start all the ambients; for (uint32 u4_c=0; u4_c=-100.0f)); Assert ((msg.fMasterVolume<=0.0f) && (msg.fMasterVolume>=-100.0f)); Assert (msg.u4SpatialType<=2); Assert (msg.fAtten>=0.0f); Assert ((msg.fOutsideVolume<=0.0f) && (msg.fOutsideVolume>=-100.0f)); Assert ((msg.fFrustumAngle>=0.0f) && (msg.fFrustumAngle<=360.0f)); // We can only have a loop stop sample for an effect Assert (msg.sndhndLoopStop == 0); u4_flags = AU_CREATE_STREAM; switch (msg.u4SpatialType) { case 0: // stereo u4_flags |= AU_CREATE_CTRL_VOLUME; break; case 1: // Pseudo u4_flags |= (AU_CREATE_PSEUDO_3D | AU_CREATE_CTRL_VOLUME); break; case 2: // spatial u4_flags |= (AU_CREATE_SPATIAL_3D | AU_CREATE_CTRL_VOLUME); break; default: Assert(0); break; } if ( (msg.u4GetAudioType() == eamVOICEOVER) && (u4GetFeatures(AUDIO_FEATURE_VOICEOVER)==0) ) { // if voiceovers are not enabled we need to create the sample so the sub title works u4_flags |= AU_CREATE_NULL; } psam = psamCreateSampleWithRetry(msg.sndhndSample, padatStreams, u4_flags); // we failed to create the sample (or maybe the loader) so we have little // option but to ignore the sound and continue the game, if (psam == NULL || bSampleError(psam)) { dprintf("Failed to create streamed sample '%x'\n", (uint32)msg.sndhndSample); return NULL; } SetupSample(psam,msg,true); return psam; } //********************************************************************************************* // Create a new voice over... // void CAudioDaemon::CreateVoiceover ( const CMessageAudio& msg ) // //************************************** { CSample* psam; psam = psamCreateMessageStream(msg); // Remeber what the voice over is... msgAudioVoiceOver = msg; // we have failed to create a sample... if (psam == NULL || bSampleError(psam)) { msgAudioVoiceOver.sndhndSample = 0; return; } if (bVoiceOver) { // a voice over is already playing so add this to a play list. This will be played // when the current voice over is done. DelayVoiceOver(psam); return; } // if there is music playing and audiable voice overs are on, adjust the music volume by 10dB if (bMusic && u4GetFeatures(AUDIO_FEATURE_VOICEOVER)) { float f_mvol = psamMusic->fGetVolume() - MUSIC_VOLUME_ADJUST; // we do not know what the music volume is so clamp it if (f_mvol<=-100.0f) f_mvol = -100.0f; psamMusic->SetVolume( f_mvol); } if (psam->bPlay(AU_PLAY_ONCE) == false) { // we have failed to play so we may as well delete the sample because it of no use // to us. delete psam; return; } // leave 2 seconds at the end to allow for timer errors, this also gives a nice delay between // queued voice over //sVoiceFinish = CMessageStep::sElapsedRealTime+psam->fSamplePlayTime()+fVOICEOVER_EXTEND; sndhndVoiceOver = msg.sndhndSample; bVoiceOver = true; psamVoiceOver = psam; } //********************************************************************************************* void CAudioDaemon::ReplayLastVoiceover ( ) //************************************** { // if a voice over is playing (or queued) or the last message contains a zero sound handle // then we cannot play the last voice over. if ((bVoiceOver) || (msgAudioVoiceOver.sndhndSample == 0)) return; CreateVoiceover(msgAudioVoiceOver); } //********************************************************************************************* // Create a new music stream // void CAudioDaemon::CreateMusic ( const CMessageAudio& msg ) // //************************************** { CSample* psam; // if music is already playing ignore the message if (bMusic) { return; } psam = psamCreateMessageStream(msg); if (psam == NULL || bSampleError(psam)) return; // if there is a voice over playing the play this music piece quiter if (bVoiceOver) { float f_mvol = psam->fGetVolume() - MUSIC_VOLUME_ADJUST; // we do not know what the music volume is so clamp it if (f_mvol<=-100.0f) f_mvol = -100.0f; psam->SetVolume( f_mvol); } if (psam->bPlay(AU_PLAY_ONCE) == false) { // we have failed to play so we may as well delete the sample because it of no use // to us. delete psam; return; } sndhndMusic = msg.sndhndSample; bMusic = true; psamMusic = psam; } //********************************************************************************************* // Create a new music stream // void CAudioDaemon::CreateAmbient ( const CMessageAudio& msg ) // //************************************** { CSample* psam; uint32 u4_activate_element; // We can only have a loop stop sample for an effect Assert (msg.sndhndLoopStop == 0); // this will either return NULL, meaning we have to load the sample or a CSample // pointer. This pointer could be an existing sample that is not playing or it // could be an instance of an exiting sample. psam = psamFindInAmbientList(msg.sndhndSample, msg.u4SpatialType, u4_activate_element); // we did not find a sample with the correct handle in the list if (psam == NULL) { uint32 u4_flags; AUMSG("Ambient Load..\n"); Assert ((msg.fVolume<=0.0f) && (msg.fVolume>=-100.0f)); Assert (msg.u4SpatialType<=2); Assert (msg.fAtten>=0.0f); Assert ((msg.fOutsideVolume<=0.0f) && (msg.fOutsideVolume>=-100.0f)); Assert ((msg.fFrustumAngle>=0.0f) && (msg.fFrustumAngle<=360.0f)); // an ambient is loaded as a defered sample, so there could be a small delay before it // is heard. u4_flags = AU_CREATE_STATIC | (msg.bDeferLoad?AU_CREATE_DEFER_LOAD_ON_PLAY:0); switch (msg.u4SpatialType) { case 0: // stereo u4_flags |= AU_CREATE_CTRL_VOLUME; break; case 1: // Pseudo u4_flags |= (AU_CREATE_PSEUDO_3D | AU_CREATE_CTRL_VOLUME); break; case 2: // spatial u4_flags |= (AU_CREATE_SPATIAL_3D | AU_CREATE_CTRL_VOLUME); break; default: Assert(0); break; } psam = CAudio::psamCreateSample(msg.sndhndSample, padatAmbient, u4_flags); if (psam == NULL || bSampleError(psam)) { // If we failed to create the sample the do nothing else dprintf("failed to create ambient..\n"); return; } } else if (bSampleError(psam)) { // we have tried to instance a sample that is still loading, just ignore the request return; } // if we do not have an element we have instanced an existing element or loaded a new one so need to add it // to the cache.. if (u4_activate_element == 0xffffffff) { // this will activate the element as well as insert it to the list u4_activate_element = AddToAmbientList(psam, msg.sndhndSample, msg.bLooped, msg.i4LoopCount, msg.fMaxDistance); } else { // start the ambient playing if we found an unused element in the ambient cache amsndAmbients[u4_activate_element].u4Flags = 0; ActivateAmbientElement(psam, msg.sndhndSample, msg.bLooped, msg.i4LoopCount, msg.fMaxDistance, u4_activate_element); } float f_master_vol = msg.fMasterVolume; amsndAmbients[u4_activate_element].fRestoreVolume = f_master_vol; // // Is this sample a permanent ambient?? If so sets its never mute flag so it will not get muted when // a dino howels. // If it is a normal ambient then clear the never mute flag so it will be quite when a dino talks. // if (msg.u4GetAudioType() == eamPERMAMBIENT) { amsndAmbients[u4_activate_element].u4Flags |= AMBIENT_NEVER_MUTE; } else { amsndAmbients[u4_activate_element].u4Flags &= ~AMBIENT_NEVER_MUTE; } if ( (bAmbientMuted) && ((amsndAmbients[u4_activate_element].u4Flags & AMBIENT_NEVER_MUTE) == 0) ) { // Ambient f_master_vol = -100.0f; amsndAmbients[u4_activate_element].u4Flags |= AMBIENT_MUTED; } else { // Our sample is not currently muted either because ambients are not in a muted state or because this sample // cannot be muted. amsndAmbients[u4_activate_element].u4Flags &= ~AMBIENT_MUTED; } // // attach the ambient sample to the correct object.. // switch (msg.u4SpatialType) { // stereo case 0: Assert(msg.pinsParent); psam->SetVolume(msg.fVolume); psam->SetMasterVolume(f_master_vol); // we attach stereo samples to obejcts just so they have a position in the world, this // has to be known so we can stop the sound if we leave the trigger AttachSoundToObject(msg.pinsParent,psam, msg.bLooped); break; // pseudo case 1: Assert(msg.pinsParent); psam->SetVolume(msg.fVolume); psam->SetMasterVolume(f_master_vol); psam->SetAttenuation(msg.fAtten, msg.fMaxVolDistance); AttachSoundToObject(msg.pinsParent,psam,msg.bLooped); break; // real 3D case 2: Assert(msg.pinsParent); psam->SetVolume(msg.fVolume); psam->SetMasterVolume(f_master_vol); psam->SetAttenuation(msg.fAtten); AttachSoundToObject(msg.pinsParent,psam, msg.bLooped); // only set the frustum if it is going to have an effect if ((msg.fFrustumAngle<360.0f) && (msg.fOutsideVolume<0.0f)) { psam->SetFrustum(msg.fFrustumAngle, msg.fOutsideVolume); } break; // unknown spatial type default: Assert(0); break; } // // Play the ambient // if (psam->bPlay(msg.bLooped?AU_PLAY_LOOPED:AU_PLAY_ONCE) == false) { // we have failed to play so we may as well delete the sample because it of no use // to us. amsndAmbients[u4_activate_element].psam = NULL; amsndAmbients[u4_activate_element].shSample = 0; amsndAmbients[u4_activate_element].u4Flags = 0; amsndAmbients[u4_activate_element].bActive = false; delete psam; return; } } //********************************************************************************************* // Process audio messages: create sounds as necessary. void CAudioDaemon::Process(const CMessageAudio& msg) { switch (msg.u4GetAudioType()) { case eamVOID: return; case eamVOICEOVER: CreateVoiceover(msg); break; case eamREPLAY_VOICEOVER: ReplayLastVoiceover(); break; case eamPERMAMBIENT: case eamAMBIENT: // if ambient sounds are not enabled, return if (u4GetFeatures(AUDIO_FEATURE_AMBIENT) == 0) return; CreateAmbient(msg); break; case eamMUSIC: // if music is not enabled, return if (u4GetFeatures(AUDIO_FEATURE_MUSIC) == 0) return; CreateMusic(msg); break; case eamATTACHED_EFFECT: case eamPOSITIONED_EFFECT: case eamDINO_VOCAL_EFFECT: bPlaySoundEffect(msg,NULL); break; case eamCTRL_FADEMUSIC: // if there is no music playing then we just ignore the fade request if (psamMusic==NULL) return; // the volume is the fader value for this message CAudio::pcaAudio->FadeSample(psamMusic, msg.fDeltaVolume, msg.bStopAfterFade); break; } } //********************************************************************************************* // CSample* CAudioDaemon::psamFindInCollisionCache ( TSoundHandle th_sound, uint32 u4_flags, uint32 u4_id, TSoundHandle sndhnd_stop ) // //************************************** { uint32 u4; uint32 u4_sample_status; uint32 u4_instance = 0xffffffff; uint32 u4_oldest_playing=0xffffffff; // index of the oldest playing sample float f_oldest_playing=CMessageStep::sElapsedRealTime+100000.0f; // time of the oldest is sometime in the future bool b_instance = false; uint32 u4_total = 0; // total number of instances of this sample that I have found CSample* psam; for (u4=0; u4u4Status(); if (u4_sample_status == AU_BUFFER_STATUS_NOTPLAYING) { //this sample is not playing so we can use it... AUMSG("Collision cache hit (direct)..\n"); acceCollisions[u4].sTime = CMessageStep::sElapsedRealTime; // Is this sample supposed to be looped?? if (u4_flags & COLLISION_CACHE_LOOPED) { Assert(u4_id != 0); if (((acceCollisions[u4].u4Flags & (COLLISION_CACHE_LOOPED|COLLISION_CACHE_LOOP_UPDATE)) != 0) && (u4_id != acceCollisions[u4].u4ID)) { // we cannot use this guy because he is flagged to be looped and the loop id is different, but we can instance from it if (acceCollisions[u4].sTime=COLLISION_CACHE_MAXINSTANCE) { AUMSG("Collision cache hit (Reuse oldest)..\n"); // we cannot instance because we have used all of the available instances for this sample so lets use // oldest one and use it again! acceCollisions[u4_oldest_playing].sTime = CMessageStep::sElapsedRealTime; acceCollisions[u4_oldest_playing].u4Flags = 0; // make sure the effect flag is set for this element.. if (u4_flags & COLLISION_CACHE_EFFECT) { acceCollisions[u4_oldest_playing].u4Flags |= COLLISION_CACHE_EFFECT; } // Is this sample supposed to be looped?? if (u4_flags & COLLISION_CACHE_LOOPED) { Assert(u4_id != 0); acceCollisions[u4_oldest_playing].u4Flags |= (COLLISION_CACHE_LOOPED|COLLISION_CACHE_LOOP_UPDATE); acceCollisions[u4_oldest_playing].u4ID = u4_id; } acceCollisions[u4_oldest_playing].shStopSample = sndhnd_stop; return acceCollisions[u4_oldest_playing].psam; } else { uint32 u4_inst_count = 0; AUMSG("Collision cache hit (Instance)..\n"); psam = acceCollisions[u4_instance].psam->psamCreateInstance(); // // If NULL is returned we cannot instance this sample so do not bother trying again, // In this case we return an error code to stop us loading it agaib. if (psam == NULL) return psamINSTANCE_IGNORE; while (bSampleError(psam) && u4_inst_count<4) { if (psam == psamINSTANCE_FAILED) { // Remove an existing instance from the cache, but not one of our own! // Only do this if the failure was due to a duplicate call failing. Do not // remove a cache entry if we failed to instance because of a loading sample. RemoveCollisionCacheEntry(acceCollisions[u4_instance].shSample); } // try to instance it again psam = acceCollisions[u4_instance].psam->psamCreateInstance(); if (psam == NULL) return psamINSTANCE_IGNORE; u4_inst_count++; } // We should now not have an error // A NULL sample cannot get to here if (!bSampleError(psam)) { AddToCollisionCache(psam, acceCollisions[u4_instance].shSample, u4_flags, u4_id, sndhnd_stop); //dprintf("Sample instanced..\n"); } return psam; } } //dprintf("Sample not instanced..\n"); // not in the cahce return NULL; } //********************************************************************************************* // void CAudioDaemon::AddToCollisionCache ( CSample* psam, TSoundHandle sh, uint32 u4_flags, uint32 u4_id, TSoundHandle sndhnd_stop ) // //************************************** { TSec s_oldest_time = CMessageStep::sElapsedRealTime + 10000.0f; // oldest time is 10000 seconds from now uint32 u4; uint32 u4_entry = 0; // We cannot specify the loop update flag Assert((u4_flags & COLLISION_CACHE_LOOP_UPDATE) == 0); for (u4=0; u4u4Status() == AU_BUFFER_STATUS_PLAYING) ) // and it is playing { continue; } if (acceCollisions[u4].sTimeu4CreateFlags & AU_CREATE_STEREO) { u4_create = AU_STEREO; } else if (acceCollisions[u4].psam->u4CreateFlags & AU_CREATE_SPATIAL_3D) { u4_create = AU_SPATIAL3D; } else if (acceCollisions[u4].psam->u4CreateFlags & AU_CREATE_PSEUDO_3D) { u4_create = AU_PSEUDO3D; } CMessageAudio msg ( acceCollisions[u4].shStopSample, eamPOSITIONED_EFFECT, // type of sound NULL, // sender (NULL if using play function) NULL, // receiver (NULL if using play function) NULL, // parent of the effect (can be NULL for stereo or positioned effects) acceCollisions[u4].psam->fGetVolume(), // volume 0dBs acceCollisions[u4].psam->fAtten, // attenuation (only for pseudo/real 3D samples) u4_create, // spatial type 360.0f, // fustrum angle (real 3D only) -15.0f, // outside volume (real 3D only) false, // looped 0, // loop count acceCollisions[u4].psam->fMaxVolDistance, // distance before sound is stopped (pseudo/real 3D only), 0, acceCollisions[u4].psam->fGetMasterVolume() ); // // Directly set the position of the sample. // This can only be done for positined effects. Attached effects must be attached to an instance // msg.SetPositionalInfo ( CVector3<>( acceCollisions[u4].psam->fWorldX, acceCollisions[u4].psam->fWorldY, acceCollisions[u4].psam->fWorldZ ) ); bImmediateSoundEffect(msg,NULL); } // If this sample is attached to something then make sure we remove it if (acceCollisions[u4].psam->u4CreateFlags & AU_ATTACHED) { // We are about to stop an attached looping sample // So remove its attachment RemoveSoundAttachment(acceCollisions[u4].psam); } //dprintf("Slide entry %x stopped\n", acceCollisions[u4].u4ID); acceCollisions[u4].psam->Stop(); acceCollisions[u4].u4ID = 0; acceCollisions[u4].u4Flags &= ~COLLISION_CACHE_LOOPED; AUMSG("Looped effect stopped.\n"); } acceCollisions[u4].u4Flags &= ~COLLISION_CACHE_LOOP_UPDATE; } } //********************************************************************************************* // This function will remove the 3D buffer from the cache in an attempt to free up hardware // resources. Collisions will be removed before effects. // This function should never be called if software 3D sound is being used. // void CAudioDaemon::RemoveCollisionCacheEntry ( TSoundHandle sndhnd_ignore // zero is the default parameter ) // //************************************** { TSec s_oldest_time = CMessageStep::sElapsedRealTime + 10000.0f; // oldest time is 10000 seconds from now TSec s_oldest_playing_time = s_oldest_time; uint32 u4; uint32 u4_entry = 0xffffffff; uint32 u4_entry_playing = 0xffffffff; // Make sure we are using hardware Assert(CAudio::pcaAudio->bUsingHardware()); for (u4=0; u4pDS3DBuffer == 0) continue; Assert(acceCollisions[u4].shSample!=0); if (sndhnd_ignore == acceCollisions[u4].shSample) continue; // Sample is playing so do not consider it if ((acceCollisions[u4].psam->u4Status() == AU_BUFFER_STATUS_PLAYING)) { if (acceCollisions[u4].sTimebDead()) { if (gpPlayer->apbbBoundaryBoxes[ebbBODY] == msg.pins1 || gpPlayer->apbbBoundaryBoxes[ebbBODY] == msg.pins2) { return; } } // // Now the real code.... Handle an audio collision sound. // float f_vel = msg.fEnergyMaxNormLog(); Assert ((f_vel<=1.0f) && (f_vel>=0.0f)); //f_vel = 1.0f; // effects are not enabled or the database is invalid if (!padatEffects || (u4FeaturesEnabled & AUDIO_FEATURE_EFFECT)==0) return; // one of the sound materials is zero, not much use to us. if ((msg.smatSound1 == 0) || (msg.smatSound2 == 0)) return; SAudioCollision* pcol = padatEffects->pcolFindCollision(u8CollisionHash(msg.smatSound1, msg.smatSound2)); // // A collision between this pair of materials was not found // if (pcol == NULL) { return; } uint32 u4_col = pcol->u4CollisionSamples(); // dout << "Normalized Vel: " << f_vel << " (Actual: " << msg.rVelocityHit << ")\n"; //dout << "Normalized Vel: " << f_vel << " (Actual: " << msg.rVelocityHit << ")\n"; // // Process the collisions, if a collision exists and the velocity is above zero // if ((msg.fEnergyMax > 0.0) && (u4_col>0)) { // // We have a valid collision but can it be used?? // if (CMessageStep::sStaticTotal>(pcol->fTimeLastUsed+pcol->fMinTimeDelay)) { // Set the last used time. pcol->fTimeLastUsed = CMessageStep::sStaticTotal; Assert(u4_col<=2); CSample* psam_mat1 = NULL; CSample* psam_mat2 = NULL; // Is the velocity of this collision above its minimum?? if (pcol->stTransfer[0].fMinVelocity< f_vel) { psam_mat1 = psamCreateCollision( pcol->sndhndSamples[0] ); } // if there is a second sample do the same again for sample 2 if (u4_col == 2) { if (pcol->stTransfer[1].fMinVelocity< f_vel) { psam_mat2 = psamCreateCollision( pcol->sndhndSamples[1] ); } } // play sample 1 if it is valid. if (psam_mat1) { psam_mat1->bPlay( pcol->stTransfer[0], f_vel, msg.v3Position.tX,msg.v3Position.tZ,msg.v3Position.tY, AU_PLAY_ONCE); // // Inform the AI system of the sound so dino's can hear, we only do this for sample 1, // sample 2 is not always present. // gaiSystem.Handle3DSound ( msg.v3Position, -40.0f + (pcol->stTransfer[0].fCalculateVolume(f_vel)*40.0f), -pcol->stTransfer[0].fAttenuate ); } // play sample 2 if it is valid and it is not a disabled terrain sound. if (psam_mat2 && (bTerrainSound || !ptCast(msg.pins2))) { psam_mat2->bPlay( pcol->stTransfer[1], f_vel, msg.v3Position.tX,msg.v3Position.tZ,msg.v3Position.tY, AU_PLAY_ONCE); } } } // // Process the slide component of this collision if one exists and the velocity // is above zero, and there is no hit component. // if ( /*(msg.fEnergyMax == 0.0) &&*/ (msg.fEnergySlide > 0.0) && (pcol->bSlide()) ) { // Make a quick ID that should be unique for all sliding collisions. uint32 u4_id = (uint32)msg.smatSound1 + (uint32)msg.smatSound2 + (uint32)msg.pins1 + (uint32)msg.pins2; /* dprintf("Frame: %d\n\n", CMessageStep::u4Frame); dprintf("Slide Mat 1: %x\n", (uint32)msg.smatSound1); dprintf("Slide Mat 2: %x\n", (uint32)msg.smatSound2); dprintf("Slide pins 1: %x\n", (uint32)msg.pins1); dprintf("Slide pins 2: %x\n", (uint32)msg.pins2); dprintf("Hash id: %x\n", (uint32)u4_id);*/ float f_slide_vel = msg.fEnergySlideNormLog(); // get the cache entry of this slide collision, we do not want a stop sample uint32 u4_entry = u4UpdateCollisionLoopStatus(u4_id); if ( u4_entry == 0xffffffff) { // We have not found the collision with the specified ID so we need to create it if (pcol->stTransfer[2].fMinVelocity< f_slide_vel) { CSample* psam_slide = psamCreateCollision( pcol->sndhndSamples[2], u4_id );; // play slide sample if it is valid. if (psam_slide) { psam_slide->bPlay( pcol->stTransfer[2], f_slide_vel, msg.v3Position.tX,msg.v3Position.tZ,msg.v3Position.tY, AU_PLAY_LOOPED); } //dprintf("Start Sample.\n"); } } else { Assert(u4_entrySetTransfer(pcol->stTransfer[2], f_slide_vel, msg.v3Position.tX,msg.v3Position.tZ,msg.v3Position.tY); //dprintf("Start Continue Sample.\n"); } #if VER_TEST else { dprintf("Null looped sample in collision array.\n"); } #endif } //dprintf("\n"); } } extern CProfileStat psMoveMsgAudio; //********************************************************************************************* // Process move message for all objects. // // The code within this message is responsible for updating the position of sounds attached to // moving objects // The position of the listener is actually the camera not the player, for it is the view of // the camera that the user sees on the screen so therefore is what sounds should be relative // to. If the camera is attached to the player or the players head then the system functions // as if the camera was on that object... // void CAudioDaemon::Process(const CMessageMove& msg_move) { CTimeBlock tmb(&psMoveMsgAudio); // If this is a sleep or wake message then we do not care. We only care about genuine // move messages. if (msg_move.etType != CMessageMove::etMOVED) return; ptr pcam = CWDbQueryActiveCamera().tGet(); // if the camera has moved update the 3D listeners position // AT THE MOMENT THIS IS A LITTLE INEFFICIENT BECAUSE THE CAMERA SENDS A MOVE // MESSAGE EVERY FRAME REGARDLESS OF WEATHER IT IS MOVING OR NOT. if (msg_move.pinsMover == (const CInstance*)pcam) { // get the position and orientation of the moved object in world space CPresence3<> p3_pres = msg_move.pinsMover->pr3Presence(); // world space vectors pointing forwards and up CVector3<> v3_in = d3YAxis * p3_pres.r3Rot; // forward vector CVector3<> v3_up = d3ZAxis * p3_pres.r3Rot; // up vector // The player has moved we need to update the position of the listener. // Positons are already in meters it is just that the axis need to be flipped around // so they match with D3D. CAudio::pcaAudio->PositionListener(p3_pres.v3Pos.tX,p3_pres.v3Pos.tZ,p3_pres.v3Pos.tY, false); CAudio::pcaAudio->OrientListener(v3_in.tX,v3_in.tZ,v3_in.tY, v3_up.tX,v3_up.tZ,v3_up.tY, false); } else { // This instance does not have a sample attached so ignore it if (msg_move.pinsMover->bIsSampleAttached() == false) return; // We can stop looking once we have found a match because an instance can only have a // single sound attached to it. for (uint32 u4_sample_count = 0; u4_sample_count p3_pres = msg_move.pinsMover->pr3Presence(); // world space vectors pointing forwards and up CVector3<> v3_in = d3YAxis * p3_pres.r3Rot; // forward vector CVector3<> v3_up = d3ZAxis * p3_pres.r3Rot; // up vector #if VER_TEST AlwaysAssert((uint32)asatSoundObjects[u4_sample_count].psam > 32); #endif asatSoundObjects[u4_sample_count].psam->SetPosition(p3_pres.v3Pos.tX,p3_pres.v3Pos.tZ,p3_pres.v3Pos.tY, false); asatSoundObjects[u4_sample_count].psam->SetOrientation(v3_in.tX,v3_in.tZ,v3_in.tY, false); return; } } } } //********************************************************************************************* // Go through the attachment list to see if any have timed out... // void CAudioDaemon::ProcessAttachments ( ) // //************************************** { for (uint32 u4_sample_count = 0; u4_sample_count0.0f) { // we are timing a sample so we use real time if (CMessageStep::sElapsedRealTime>asatSoundObjects[u4_sample_count].sDetatchTime) { // It is time for this sample to be detached and this element freed. asatSoundObjects[u4_sample_count].pins->SetSampleAttached(false); asatSoundObjects[u4_sample_count].pins = NULL; asatSoundObjects[u4_sample_count].sDetatchTime = 0.0f; asatSoundObjects[u4_sample_count].psam->u4CreateFlags &= ~AU_ATTACHED; asatSoundObjects[u4_sample_count].psam = NULL; } } } } } //********************************************************************************************** // Attach a sound to an object, the location and orientation of the sound is based on the // presence of the instance. An instance can only have one sample attached to it. // void CAudioDaemon::AttachSoundToObject ( CInstance* pins, CSample* psam, bool b_sample_looped ) // //************************************** { uint32 u4_index = 0xffffffff; for (uint32 u4_sample_count = 0; u4_sample_count p3_pres = pins->pr3Presence(); // world space vectors pointing forwards and up CVector3<> v3_in = d3YAxis * p3_pres.r3Rot; // forward vector // Position the sample asatSoundObjects[u4_index].psam->SetPosition(p3_pres.v3Pos.tX,p3_pres.v3Pos.tZ,p3_pres.v3Pos.tY, true); asatSoundObjects[u4_index].psam->SetOrientation(v3_in.tX,v3_in.tZ,v3_in.tY, true); // Mark the sample as attached to something asatSoundObjects[u4_index].psam->u4CreateFlags |= AU_ATTACHED; // Update the partition so we know that this sample is attached pins->SetSampleAttached(true); // set the detatch time if (b_sample_looped) { asatSoundObjects[u4_index].sDetatchTime = -1.0f; // negative time means do not detatch. } else { asatSoundObjects[u4_index].sDetatchTime = CMessageStep::sStaticTotal + psam->fSamplePlayTime(); } } } //********************************************************************************************** // This will remove the attachment for the specified sample, only 1 object can be attached to // a specific sample. // void CAudioDaemon::RemoveSoundAttachment ( CSample* psam ) // //************************************** { for (uint32 u4_sample_count = 0; u4_sample_countSetSampleAttached(false); asatSoundObjects[u4_sample_count].psam->u4CreateFlags &= ~AU_ATTACHED; asatSoundObjects[u4_sample_count].psam = NULL; asatSoundObjects[u4_sample_count].pins = NULL; asatSoundObjects[u4_sample_count].sDetatchTime = 0.0f; return; } } } //********************************************************************************************** CInstance* CAudioDaemon::pinsFindSampleAttachment ( CSample* psam ) const // //************************************** { for (uint32 u4_sample_count = 0; u4_sample_countSetSampleAttached(false); asatSoundObjects[u4_sample_count].psam->u4CreateFlags &= ~AU_ATTACHED; asatSoundObjects[u4_sample_count].psam = NULL; asatSoundObjects[u4_sample_count].pins = NULL; asatSoundObjects[u4_sample_count].sDetatchTime = 0.0f; return; } } } //********************************************************************************************** // This used to delay a voice over when another is already playing. The CSample that contains // the voice over is added to a list which is processed when the current voice over finishes. // void CAudioDaemon::DelayVoiceOver ( CSample* psam ) // //************************************** { for (uint32 u4=0; u40); // if we have a loop count the end time is sample length * loop count amsndAmbients[u4_activate].sEndTime = CMessageStep::sElapsedRealTime + (psam->fSamplePlayTime() * i4_loop_count); } amsndAmbients[u4_activate].u4Flags |= AMBIENT_LOOP; } else { // if we are not looped then the end time is the current time+sample time amsndAmbients[u4_activate].sEndTime = CMessageStep::sElapsedRealTime+psam->fSamplePlayTime(); amsndAmbients[u4_activate].u4Flags &= ~AMBIENT_LOOP; } amsndAmbients[u4_activate].fMaxDistance = f_dist*f_dist; amsndAmbients[u4_activate].psam = psam; amsndAmbients[u4_activate].bActive = true; amsndAmbients[u4_activate].shSample = sh; } //********************************************************************************************** // This function will add the specified ambient to the current ambient list // It returns the cache element that we put it in, so we can modify it later // uint32 CAudioDaemon::AddToAmbientList ( CSample* psam, TSoundHandle sh, bool b_loop, int32 i4_loop_count, float f_dist ) // //************************************** { uint32 u4_inactive = 0xffffffff; TSec s_oldest = CMessageStep::sElapsedRealTime + 100000000.0f; // some time in the future AUMSG("Insert Ambient..\n"); // we have no element so we have to search for an old one... uint32 u4_sample_count; for (u4_sample_count = 0; u4_sample_countbSampleLoading()) { AUMSG("Ambient loading, skipped..\n"); continue; } // get the position of the sample... f_x = amsndAmbients[u4_sample_count].psam->fWorldX; f_y = amsndAmbients[u4_sample_count].psam->fWorldY; f_z = amsndAmbients[u4_sample_count].psam->fWorldZ; // update the time stap of this sample.. amsndAmbients[u4_sample_count].sTime = CMessageStep::sElapsedRealTime; if ((CMessageStep::sElapsedRealTime > amsndAmbients[u4_sample_count].sEndTime) || (CAudio::pcaAudio->fGetSquaredListenerDistance(f_x, f_y, f_z) > amsndAmbients[u4_sample_count].fMaxDistance)) { AUMSG("Suspend ambient..\n"); // either we are out of range or we have come to the end of the sample, mark the sample as inactive, // detach it from its object and stop it, but do not delete it. amsndAmbients[u4_sample_count].psam->Stop(); RemoveSoundAttachment(amsndAmbients[u4_sample_count].psam); amsndAmbients[u4_sample_count].bActive = false; } } } } //********************************************************************************************** uint32 CAudioDaemon::u4CountAmbientSounds ( ) const // //************************************** { uint32 u4_total = 0; for (uint32 u4_sample_count = 0; u4_sample_countu4CreateFlags & u4_create_flag) { // // the activate element is required for the add to list function as it needs to // know which element in the cahce to activate. It could search for it but what // is the point when we already know? // u4_active_element = u4_sample_count; return amsndAmbients[u4_sample_count].psam; } AUMSG("Ambient type mismatch - must instance\n"); // // This sample is the correct sample and it is inactive but it is not the correct // spatial type so returning it would probably crash. // We know what the create flags of the new buffer should be so lets instance it // to a different spatial type, this will save us reloading. // if (!b_instance) { b_instance = true; u4_instance = u4_sample_count; } } } } // did we find an instance?? if (b_instance) { // yes, we have to instance. We have been all the way through the cache and did not find // a sample of the correct type that was inactive. So we will make an instance of an // active one. // If we try to instance a deferred loading sample, the instance function will return // psamDEFER_LOAD and it is the responsibility of the caller to take special action. AUMSG("Ambient cache hit (Instance)...\n"); psam = amsndAmbients[u4_instance].psam->psamCreateInstance(u4_create_flag); // if we have failed to create the instance, assume it does not exist if (psam == psamINSTANCE_FAILED) return NULL; return psam; } // not in the list.. return NULL; } //********************************************************************************************** // Voice overs once killed can be deleted because they will not be required again. // void CAudioDaemon::KillVoiceovers ( ) //************************************* { if (bVoiceOver) { Assert (psamVoiceOver); RemoveSoundAttachment(psamVoiceOver); delete psamVoiceOver; bVoiceOver = false; } if (bDelayedVoiceOver) { // delete any delayed voiceovers bDelayedVoiceOver = false; for (uint32 u4_sample_count = 0; u4_sample_countStop(); amsndAmbients[u4_sample_count].bActive = false; RemoveSoundAttachment(amsndAmbients[u4_sample_count].psam); } } } //********************************************************************************************** // Music once killed can be deleted as it is never cached // void CAudioDaemon::KillMusic ( ) //************************************* { if (bMusic) { Assert(psamMusic); // remove from the fade list just in case it is fading CAudio::pcaAudio->StopFade(psamMusic); RemoveSoundAttachment(psamMusic); delete psamMusic; bMusic = false; } } //********************************************************************************************** // To kill the collisions we just go through the list and stop them all, in this way they all // remain in the cahce for future use. // void CAudioDaemon::KillCollisions ( bool b_delete ) //************************************* { for (uint32 u4_sample_count=0; u4_sample_countStop(); // If we are to delete the item, kill its cahce entry and delete the sample. if (b_delete) { acceCollisions[u4_sample_count].shSample = 0; acceCollisions[u4_sample_count].shStopSample = 0; acceCollisions[u4_sample_count].sTime = 0.0f; acceCollisions[u4_sample_count].u4ID = 0; RemoveSoundAttachment(acceCollisions[u4_sample_count].psam); delete acceCollisions[u4_sample_count].psam; acceCollisions[u4_sample_count].psam = NULL; } } } } //********************************************************************************************** // Removes any existing subtitle // void CAudioDaemon::RemoveSubtitle ( ) //************************************* { CTextOverlay::ptovTextSystem->RemoveText(ettSUBTITLE); } //********************************************************************************************** // When a voiceover is played, if subtitles are enabled, this function is called with a pointer // to the sample. From this the subtitle data can be extracted and formatted. // void CAudioDaemon::ProcessSubtitle ( CSample* psam ) //************************************* { Assert(psam); // this sample has no subtitle... if (psam->pasubSubtitle == NULL) return; if (psam->pasubSubtitle->bSubtitleSetup()) return; // Mark the sub title as been setup... psam->pasubSubtitle->SubtitleSetup(); SSubtitleHeader* psth = psam->pasubSubtitle->psthGetSubtitleData(); // there is no subtitle if (psth == NULL) { // we can leave any existing subtitle on the screen. return; } uint32 u4_sect = psth->u4SubtitleSections; // skip the header to point to the first section of data. // sections in the source data are formatted as follows: // +0 time in 1/5s of a second. // +4 Raw string that may contain 0x13 charcaters for new lines // String is zero terminated. // +n After terminator is another time block (NOT ALIGNED) // +n+4 String of section section... // ... // ... // Repeat for the number of sections in the subtitle... // // // The data in the destination buffer in almost exactly the same format but the strings // have been formatted. For infomration on the formatting see the DDFont code. // uint8* pu1_data = (uint8*)(psth+1); uint32 u4_prev = CTextOverlay::ptovTextSystem->u4FindSequenceEnd(ettSUBTITLE); TSec s_time; // go through all of the sections in the data block... while(u4_sect) { s_time = (float)(*(uint32*)pu1_data)*0.2f; pu1_data+=sizeof(uint32); u4_prev = CTextOverlay::ptovTextSystem->u4DisplayFormattedString((char*)pu1_data, s_time, TEXT_FORMAT_BOTTOM|TEXT_FORMAT_CENTER, CColour(255,255,255), u4_prev, ettSUBTITLE); // skip over the string and the NULL so we point to the next buffer. pu1_data+=strlen((char*)pu1_data)+1; u4_sect--; } } //********************************************************************************************** bool CAudioDaemon::bPlaySoundEffect ( const CMessageAudio& msg, SSoundEffectResult* pser ) { // this function can only play effects Assert((msg.u4GetAudioType() == eamATTACHED_EFFECT) || (msg.u4GetAudioType() == eamPOSITIONED_EFFECT) || (msg.u4GetAudioType()==eamDINO_VOCAL_EFFECT) ); CSample* psam; // if effects are not enabled then do nothing if (u4GetFeatures(AUDIO_FEATURE_EFFECT) == 0) return true; // make an Id that identifies this instance of this sample uint32 u4_id = (uint32)msg.sndhndSample + (uint32)msg.pinsParent; // If this is a looped effect then chekc the collision loop flags if (msg.bLooped) { if (u4UpdateCollisionLoopStatus(u4_id)!=0xffffffff) { // If we update an already looping sample then return a NULL handle // and a zero length; if (pser) { pser->u4SoundID = 0; } return true; } } else { // we can only have a stop sample on a looped effect Assert(msg.sndhndLoopStop == 0); } // look for a sample with the given properties in the collision/effect cache. // this will create an instance of the sample if it is in the cache but is in use, it may // also change the state of an collision sample to be an effect sample... // If the sample is found in the cahce and it is looped then the looped flag is updated psam = psamFindInCollisionCache(msg.sndhndSample, COLLISION_CACHE_EFFECT | (msg.bLooped?COLLISION_CACHE_LOOPED:0), u4_id,msg.sndhndLoopStop); /// the cahce entry we may have found could have the wrong buffer type.. #if VER_DEBUG if (((uint32)psam>3) && CAudio::pcaAudio->bAudioActive()) { switch (msg.u4SpatialType) { case 0: // stereo Assert(psam->pDS3DBuffer == NULL); break; case 1: // Pseudo Assert(psam->pDS3DBuffer == NULL); break; case 2: // spatial Assert(psam->pDS3DBuffer != NULL) break; } } #endif // if we did not find sample 1 in the cache, load it and add it to the cache if (psam == NULL) { uint32 u4_flags = AU_CREATE_STATIC|(msg.bDeferLoad?AU_CREATE_DEFER_LOAD_ON_PLAY:0); switch (msg.u4SpatialType) { case 0: // stereo u4_flags |= AU_CREATE_CTRL_VOLUME; break; case 1: // Pseudo u4_flags |= (AU_CREATE_PSEUDO_3D | AU_CREATE_CTRL_VOLUME); break; case 2: // spatial u4_flags |= (AU_CREATE_SPATIAL_3D | AU_CREATE_CTRL_VOLUME); break; default: Assert(0); break; } AUMSG("Loading Effect sample...\n"); psam = psamCreateSampleWithRetry(msg.sndhndSample,padatEffects, u4_flags); // if loaded a valid sample add it to the cache for future use and flag it if it is looped if (psam!=NULL && !bSampleError(psam)) AddToCollisionCache(psam, msg.sndhndSample, COLLISION_CACHE_EFFECT|(msg.bLooped?COLLISION_CACHE_LOOPED:0),u4_id,msg.sndhndLoopStop); } // the sample pointer we have is valid if (psam != NULL && !bSampleError(psam)) { SetupSample ( psam, msg, ((msg.u4GetAudioType() == eamATTACHED_EFFECT) || (msg.u4GetAudioType() == eamDINO_VOCAL_EFFECT)) // Should we attach the sample to the object?? ); psam->bPlay(msg.bLooped?AU_PLAY_LOOPED:AU_PLAY_ONCE); if (pser) { pser->u4SoundID = (uint32)psam; } // if we are playing a dino effect then we need to mute any ambient sounds if (msg.u4GetAudioType()==eamDINO_VOCAL_EFFECT) MuteAmbientSounds( psam->fSamplePlayTime() ); return true; } // // if we get to here we have failed to play the requested sample // if (pser) { pser->u4SoundID = 0; } return false; } //********************************************************************************************** // This will mute any mutable ambient sounds while the dinos howel void CAudioDaemon::MuteAmbientSounds ( float f_play_time ) //************************************* { // if ambients are not enabled then do nothing if ((u4FeaturesEnabled & AUDIO_FEATURE_AMBIENT) == 0) return; if (bAmbientMuted) { // AmbientSounds are already muted so lets just check the ending time if (CMessageStep::sStaticTotal + (f_play_time*1.25) > sAmbientRestore) sAmbientRestore = CMessageStep::sStaticTotal + (f_play_time*1.25); } else { // Ambients are not muted so we need to mute them and set the restore time bAmbientMuted = true; // The time when ambients should be restored... sAmbientRestore = CMessageStep::sStaticTotal + (f_play_time*1.25); for (uint32 u4_sample_count = 0; u4_sample_countfGetMasterVolume(); // set the master volume to nothing so we cannot hear anything amsndAmbients[u4_sample_count].psam->SetMasterVolume(-100.0); // This sample has been muted amsndAmbients[u4_sample_count].u4Flags |= AMBIENT_MUTED; } } } } } //********************************************************************************************** // This will restore any muted ambient samples to their original volume... void CAudioDaemon::RestoreAmbientSounds ( ) //************************************* { for (uint32 u4_sample_count = 0; u4_sample_countSetMasterVolume(amsndAmbients[u4_sample_count].fRestoreVolume); } } } bAmbientMuted = false; } //********************************************************************************************** // This will stop the specified sound effect. // Before we call psam->Stop() we must check that the sample is still within the cache. If the // sample is not present within the cahce then do not stop it as something else has removed it // from the cahce and it is likely that the psam has been deleted. void CAudioDaemon::StopSoundEffect ( CSample* psam ) //************************************* { Assert(psam); for (uint32 u4_sample_count=0; u4_sample_countStop(); acceCollisions[u4_sample_count].u4Flags = 0; // We may as well return because there cannot be two identical CSample addresses // in the cahce return; } } } //********************************************************************************************** void CAudioDaemon::SetupSample ( CSample* psam, const CMessageAudio& msg, bool b_attach ) //************************************* { Assert ((msg.fVolume<=0.0f) && (msg.fVolume>=-100.0f)); Assert (msg.u4SpatialType<=2); Assert (msg.fAtten>=0.0f); Assert ((msg.fOutsideVolume<=0.0f) && (msg.fOutsideVolume>=-100.0f)); Assert ((msg.fFrustumAngle>=0.0f) && (msg.fFrustumAngle<=360.0f)); switch (msg.u4SpatialType) { // stereo case 0: psam->SetVolume(msg.fVolume); psam->SetMasterVolume(msg.fMasterVolume); break; // pseudo case 1: psam->SetAttenuation(msg.fAtten); if (b_attach) { Assert(msg.pinsParent); AttachSoundToObject(msg.pinsParent,psam); } else { // If the message contains an absolute position then we just set it, if this is // not the case we must have an instance to attach the sound to. if (msg.bPosition()) { psam->SetPosition(msg.v3Pos.tX,msg.v3Pos.tZ,msg.v3Pos.tY, true); } else { Assert(msg.pinsParent); CPresence3<> p3_pres = msg.pinsParent->pr3Presence(); CVector3<> v3_in = d3YAxis * p3_pres.r3Rot; // forward vector psam->SetPosition(p3_pres.v3Pos.tX,p3_pres.v3Pos.tZ,p3_pres.v3Pos.tY, true); psam->SetOrientation(v3_in.tX,v3_in.tZ,v3_in.tY, true); } } psam->SetMasterVolume(msg.fMasterVolume); break; // real 3D case 2: psam->SetVolume(msg.fVolume); psam->SetAttenuation(msg.fAtten); psam->SetMasterVolume(msg.fMasterVolume); // only set the frustum if it is going to have an effect if ((msg.fFrustumAngle<360.0f) && (msg.fOutsideVolume<0.0f)) { psam->SetFrustum(msg.fFrustumAngle, msg.fOutsideVolume); } if (b_attach) { Assert(msg.pinsParent); AttachSoundToObject(msg.pinsParent,psam); } else { // If the message contains an absolute position then we just set it, if this is // not the case we must have an instance to attach the sound to. if (msg.bPosition()) { psam->SetPosition(msg.v3Pos.tX,msg.v3Pos.tZ,msg.v3Pos.tY, true); gaiSystem.Handle3DSound(msg.v3Pos,-msg.fAtten,msg.fMasterVolume+msg.fVolume); } else { Assert(msg.pinsParent); CPresence3<> p3_pres = msg.pinsParent->pr3Presence(); CVector3<> v3_in = d3YAxis * p3_pres.r3Rot; // forward vector psam->SetPosition(p3_pres.v3Pos.tX,p3_pres.v3Pos.tZ,p3_pres.v3Pos.tY, true); psam->SetOrientation(v3_in.tX,v3_in.tZ,v3_in.tY, true); gaiSystem.Handle3DSound(p3_pres.v3Pos,-msg.fAtten,msg.fMasterVolume+msg.fVolume); } } break; // unknown spatial type default: Assert(0); break; } } //********************************************************************************************** // This will create a sample and if it fails will check if hardware is being used. If hardware // is being used it will release some resources and try again. This will be repeated 4 time and // then a software buffer of the same type is created which should not fail. // The checking is only done for spatial 3D buffers normal sound buffers should never fail on // the first attempt // CSample* CAudioDaemon::psamCreateSampleWithRetry ( TSoundHandle sndhnd, CAudioDatabase* padat, uint32 u4_flags ) //************************************* { //dprintf("Create Sample..\n"); CSample* psam = CAudio::psamCreateSample( sndhnd, padat, u4_flags); uint32 u4_count; bool b_buffer; // // If we have a valid sample pointer it may still not be a valid sample because the DS pointer // may be NULL if (psam) { if (CAudio::pcaAudio->bUsingHardware() && (u4_flags & AU_CREATE_SPATIAL_3D)) { // // If we are using hardware then the buffer may have failed to be created so we need to delete // something from the cahce and try again. // // Have 5 attempts at creating the buffer buffer // b_buffer = (psam->pDSBuffer != NULL); u4_count = 0; while ((b_buffer==false) && (u4_count<=4)) { if (psam->pDSBuffer == NULL) { // // We have failed to allocate a 3D buffer, lets remove 1 from the cache to free up resources // delete psam; psam = NULL; RemoveCollisionCacheEntry(); b_buffer = false; } //dprintf("HW Load fail: Evicting and retrying\n"); AUMSG("HW Load fail: Evicting and retrying\n"); // On the last time around create the buffer as a software 3D buffer. psam = CAudio::psamCreateSample( sndhnd, padat, u4_flags | ((u4_count<4)?0:AU_CREATE_FORCE_SOFTWARE)); // A null psam is a bad thing, just break out of the loop if (psam == NULL) return NULL; //dprintf("CreateSample returned..\n"); if (psam->pDSBuffer) break; u4_count++; } } } return psam; } //********************************************************************************************** // This is called when a sim stop message is received so all playing audio can be paused.... void CAudioDaemon::PauseAllPlayingAudio ( ) //************************************* { uint32 u4; // // Stop collisions, this is only important for looping and long samples but we may as well // stop them all. This does not remove any samples, it simply stops them and clears their // looping status // for (u4=0; u4Stop(); acceCollisions[u4].u4Flags &= ~(COLLISION_CACHE_LOOP_UPDATE|COLLISION_CACHE_LOOPED); } } // // To stop the voice over simply stop the sample and the loader thread will stop loading // data // if (bVoiceOver) { Assert (psamVoiceOver); psamVoiceOver->Stop(); } // // Stop music // if (bMusic) { Assert(psamMusic); psamMusic->Stop(); } // // Stop ambients // for (u4 = 0; u4Stop(); } } } //********************************************************************************************** // This is called when a sim start message is received, all samples that are paused should be // restarted. void CAudioDaemon::RestartPausedAudio ( ) //************************************* { uint32 u4; // Collisions are not restarted, they should simply sort themselves out when the sim is // going. This also goes for looped samples as they send a collision message every frame. // // If bVoiceOver is set then a voice over is part way through and needs to be restarted... // if (bVoiceOver) { Assert (psamVoiceOver); psamVoiceOver->bPlay(AU_PLAY_RESUME); } // // If bMusic is set then a music stream is part way through and needs to be restarted... // if (bMusic) { Assert(psamMusic); psamMusic->bPlay(AU_PLAY_RESUME); } // // Restart ambients // for (u4 = 0; u4bPlay(AU_PLAY_RESUME | (amsndAmbients[u4].u4Flags & AMBIENT_LOOP?AU_PLAY_LOOPED:0) ); } } } //********************************************************************************************** // Open the three permenant packed audio files either from the local hard disk or from M: // on the network. void CAudioDaemon::OpenAudioDatabases ( ) //************************************* { padatEffects = OpenDatabase("Effects.tpa", 16); // Simultaneous effects are requred, so not share handles padatStreams = OpenDatabase("Stream.tpa", 8); // Simultaneous streams are required, do not share file handle padatAmbient = OpenDatabase("Ambient.tpa", 10); // Simultaneous ambient are required, do not share file handle } //********************************************************************************************** // Close the three packed audio files void CAudioDaemon::CloseAudioDatabases ( ) //************************************* { delete padatEffects; padatEffects = NULL; delete padatStreams; padatStreams = NULL; delete padatAmbient; padatAmbient = NULL; } //********************************************************************************************** // Create a database from the specified file, it will first look locally and the it will // look on M: CAudioDatabase* CAudioDaemon::OpenDatabase ( const char* str_name, uint32 u4_handles ) //************************************* { char str_path[MAX_PATH]; const char * str_file = str_name; // // Look for the specified file... // if (!bFileExists(str_file)) { // if we do not find it, path a netwrok filename and look for that strcpy(str_path, str_DataPath); //strcpy(str_path,"A:\\"); strcat(str_path,str_name); str_file = str_path; // if we still do not find it it must not exist if (!bFileExists(str_file)) { // return no database return NULL; } } // // str_file points to the filename of the database we should load // return new CAudioDatabase(str_file, u4_handles); } //********************************************************************************************** // HACK HACK HACK // This can be called directly if you requires the return result.. This is against the message // protocol but with a message it is a pain in the ass to pass a return result. bool bImmediateSoundEffect ( const CMessageAudio& msg, SSoundEffectResult* pser ) //************************************* { return padAudioDaemon->bPlaySoundEffect(msg,pser); } //********************************************************************************************** void StopSoundEffect ( uint32 u4_id ) //************************************* { padAudioDaemon->StopSoundEffect((CSample*)u4_id); } //********************************************************************************************** bool bQuerySoundEffect ( TSoundHandle sndhnd, SSoundEffectResult* pser ) //************************************* { SSampleFile* psf; if (padAudioDaemon->padatEffects == NULL) return false; psf = padAudioDaemon->padatEffects->psfFindSample(sndhnd); if (psf) { // calculate the play time from the decompressed sample length and the sample format pser->fPlayTime = (float)psf->cauheaderIndex.u4DecompressedSize / (float)(psf->cauheaderIndex.u4Frequency*(psf->cauheaderIndex.u1Bits/8)*psf->cauheaderIndex.u1Channels); pser->fMasterVolume = psf->fMasterVolume; pser->fAttenuation = psf->fAttenuation; return true; } // Sample not found so return zero return false; } //********************************************************************************************** // void InitAudioDaemon ( CWorld* pw ) //************************************* { // Make sure the audio system is going AlwaysAssert(CAudio::pcaAudio != NULL); // Create audio entity. padAudioDaemon = new CAudioDaemon(); // Since this function should be called from within CWorld::Init(), the database should already be locked. Assert(pw->bIsLocked()); pw->AddWithNoLock(padAudioDaemon); }