Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Class Members | File Members | Related Pages

AudioManager.cpp

Go to the documentation of this file.
00001 /*
00002  * AudioManager.cpp
00003  *
00004  * Part of Fly! Legacy project
00005  *
00006  * Copyright 2003-2004 Chris Wallace
00007  *
00008  * Fly! Legacy is free software; you can redistribute it and/or modify
00009  *   it under the terms of the GNU General Public License as published by
00010  *   the Free Software Foundation; either version 2 of the License, or
00011  *   (at your option) any later version.
00012  *
00013  * Fly! Legacy is distributed in the hope that it will be useful,
00014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016  *   GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  *   along with Fly! Legacy; if not, write to the Free Software
00020  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00021  */
00022 
00027 /*
00028  * Audio Manager
00029  *
00030  * Discrete sound sources must be provided for:
00031  *   - 2 x COM radios
00032  *   - 2 x NAV radios
00033  *   - 2 x XPDR radios
00034  *   - panels (one source or more?)
00035  *   - individual engines
00036  *   - individual hydraulic systems (e.g. flaps, landing gear)
00037  *   - DLLs
00038  *
00039  * Since the audio manager creates the OpenAL context, there can only be
00040  *   one instance of the CAudioManager class.  The audio manager should
00041  *   therefore also manage OpenAL sources.  The SDK audio API is not very
00042  *   well suited to this approach, it implies that source attributes
00043  *   such as position, velocity etc. should be modified on a per-sound-effect
00044  *   (WAV file) basis.  This would be difficult to do under OpenAL however.
00045  *   For the time being, applications must first request an audio source
00046  *   in which to play their sound effect.  Calling the PlaySfx method
00047  *   returns a key to the audio buffer.
00048  */
00049 
00050 #include "../Include/AudioManager.h"
00051 #include "../Include/Globals.h"
00052 
00053 using namespace std;
00054 
00055 
00056 //
00057 // CSoundEffect methods
00058 //
00059 // A sound effect consists of:
00060 //   - WAV filename
00061 //   - OpenAL buffer
00062 //   - Two int user data values
00063 //   - Reference to the OpenAL source that the sound effect is bound to (if any)
00064 //
00065 
00066 CSoundEffect::CSoundEffect (void)
00067   : bound (false), name(0), user1(0), user2(0)
00068 {
00069 #ifdef HAVE_OPENAL
00070   source = buffer = 0;
00071 #endif // HAVE_OPENAL
00072 
00073   strcpy (wavfile, "");
00074 }
00075 
00076 CSoundEffect::~CSoundEffect (void)
00077 {
00078 
00079 #ifdef HAVE_OPENAL
00080 
00081   if (bound) {
00082     // Sound effect is bound to a source and may be playing
00083     alSourceStop (source);
00084     alSourcei (source, AL_BUFFER, 0);
00085   }
00086 
00087   // Delete OpenAL buffer
00088   alDeleteBuffers (1, &buffer);
00089 
00090 #endif // HAVE_OPENAL
00091 }
00092 
00093 
00094 //
00095 // CAudioManager
00096 //
00097 
00098 
00099 //
00100 // Constructor
00101 //
00102 CAudioManager::CAudioManager (void)
00103 {
00104 }
00105 
00106 //
00107 // Destructor
00108 //
00109 CAudioManager::~CAudioManager (void)
00110 {
00111   // Destroy all cached sound effects
00112   map<string,CSoundEffect*>::iterator iter;
00113   for (iter = cache.begin(); iter != cache.end(); iter++) {
00114     CSoundEffect *sfx = iter->second;
00115     delete sfx;
00116   }
00117 
00118 #ifdef HAVE_OPENAL
00119   // Clean up AL context and device
00120   context = alcGetCurrentContext ();
00121   device = alcGetContextsDevice (context);
00122   alcMakeContextCurrent (NULL);
00123   alcDestroyContext (context);
00124   alcCloseDevice (device);
00125 #endif // HAVE_OPENAL
00126 }
00127 
00128 
00129 //
00130 // Initialization
00131 // 
00132 // This method must be called immediately after instantiation of the audio
00133 // manager class
00134 //
00135 void CAudioManager::Init (void)
00136 {
00137 #ifdef HAVE_OPENAL
00138   ALint error;
00139 
00140   // Open default AL device
00141   device = alcOpenDevice (NULL);
00142   if (device == NULL) {
00143     // Could not open audio device
00144     gtfo ("CAudioManager::Init : Could not open default audio device");
00145   }
00146 
00147   // Create context
00148   context = alcCreateContext (device, NULL);
00149   if (context == NULL) {
00150     // Could not create context
00151     gtfo ("CAudioManager::Init : Could not create context");
00152   }
00153 
00154   // Set context as the active one
00155   alcGetError (device);
00156   alcMakeContextCurrent (context);
00157   if (alcGetError (device) != ALC_NO_ERROR) {
00158     gtfo ("CAudioManager::Init : Could not make context current");
00159   }
00160 
00161   // Clear error code
00162   alGetError ();
00163   alcGetError (device);
00164 
00165   // Listener position is always set to the origin of 3D audio space.
00166   // All sound effects must be position relative to the origin
00167   ALfloat listenerPos[] = {0.0, 0.0, 0.0};
00168   alListenerfv (AL_POSITION, listenerPos);
00169   if ((error = alGetError ()) != AL_NO_ERROR) {
00170     gtfo ("CAudioManager::Init :  Could not set listener position");
00171   }
00172 
00173   // Default listener velocity is zero.
00174   ALfloat listenerVel[] = {0.0, 0.0, 0.0};
00175   alListenerfv (AL_VELOCITY, listenerVel);
00176   if ((error = alGetError ()) != AL_NO_ERROR) {
00177     gtfo ("CAudioManager::Init :  Could not set listener velocity");
00178   }
00179 
00180   // Listener orientation is facing "into" the screen, towards +z axis
00181   // with +y axis as the up vector
00182   ALfloat listenerOrient[] = {0.0, 0.0, 1.0, 0.0, 1.0, 0.0}; // Fwd, up
00183   alListenerfv (AL_ORIENTATION, listenerOrient);
00184   if ((error = alGetError ()) != AL_NO_ERROR) {
00185     gtfo ("CAudioManager::Init :  Could not set listener orientation");
00186   }
00187 #endif // HAVE_OPENAL
00188 }
00189 
00190 
00191 //
00192 // SetListenerPosition
00193 //
00194 // This method sets the listener's position in Fly! geodetic coordinates.
00195 // For the purposes of distance attenuation, the listener position is always
00196 // assumed to be at the "origin" of the 3D world.  Setting the listener
00197 // position is only required if SetSfxPosition () method is used to set
00198 // sound effect positions using geodetic coordinates.  If there are no
00199 // position-dependent sound effects to be played (not likely) or if the
00200 // sound effect positions will always be specified using the
00201 // SetSfxPosition (SVector) method, then the application does not need to
00202 // continuously update the listener world position by calling this function.
00203 //
00204 void CAudioManager::SetListenerPosition (SPosition *p)
00205 {
00206   listenerPos.lat = p->lat;
00207   listenerPos.lon = p->lon;
00208   listenerPos.alt = p->alt;
00209 }
00210 
00211 
00212 //
00213 // CreateSource
00214 //
00215 int CAudioManager::CreateSource (void)
00216 {
00217 #ifdef HAVE_OPENAL
00218 
00219   ALuint source;
00220   ALint  error;
00221   alGetError ();
00222   alGenSources (1, &source);
00223   if ((error = alGetError ()) != AL_NO_ERROR) {
00224     gtfo ("CAudioManager::CreateSource : Error 0x%08X", error);
00225   }
00226 
00227   return source;
00228 #else // HAVE_OPENAL
00229 
00230   return 0;
00231 
00232 #endif
00233 }
00234 
00235 
00236 //
00237 // SetSourcePosition
00238 //
00239 
00240 
00241 //
00242 // CreateSfx
00243 //
00244 // This method allocates a new sound effect and returns its key value.
00245 //   The specified WAV file must exist in the pod filesystem.
00246 //
00247 // Return Code:
00248 //   int    Unique "name" of the sound effect, used as a key in other methods.
00249 //          Return value is 0 if the sound effect could not be created
00250 //
00251 int CAudioManager::CreateSfx (PFS *pfs, const char* wavfile)
00252 {
00253   int rc = 0;
00254   bool cached = false;
00255 
00256   // Search existing sound effects to see if this has already been created
00257   map<string,CSoundEffect*>::iterator iter = cache.find (wavfile);
00258   if (iter != cache.end()) {
00259     CSoundEffect *sfx = iter->second;
00260     rc = sfx->name;
00261     cached = true;
00262   }
00263 
00264   // If sound effect is not already cached then try to load it
00265   if (!cached) {
00266     // Open WAV file
00267     PODFILE *p = popen (pfs, wavfile);
00268     if (p == NULL) {
00269       // Try prepending SOUND\ path to wav filename
00270       char wavfilepath [256];
00271       strcpy (wavfilepath, "SOUND\\");
00272       strcat (wavfilepath, wavfile);
00273       p = popen (pfs, wavfilepath);
00274       if (p == NULL) {
00275         // No luck
00276         gtfo ("CAudioManager::CreateSfx : Cannot open WAV file %s", wavfile);
00277       }
00278     }
00279 
00280     // Transfer WAV contents into temporary buffer
00281     int wavSize = p->size;
00282     char *wavBuffer = new char[wavSize];
00283     pread (wavBuffer, 1, wavSize, p);
00284  
00285 #ifdef HAVE_OPENAL
00286 
00287     // Load the WAV from the memory buffer
00288     void *data;
00289     ALenum format;
00290     ALsizei size;
00291     ALsizei freq;
00292     ALboolean loop;
00293     ALenum error;
00294     alGetError ();
00295     alutLoadWAVMemory (wavBuffer, &format, &data, &size, &freq, &loop);
00296     if ((error = alGetError()) != AL_NO_ERROR) {
00297       gtfo ("CAudioManager::CreateSfx : Load WAV error 0x%08X", error);
00298     }
00299 
00300     // Create buffer object and load it with the WAV contents
00301     ALuint buffer;
00302     alGetError ();
00303     alGenBuffers (1, &buffer);
00304     if ((error = alGetError()) != AL_NO_ERROR) {
00305       gtfo ("CAudioManager::CreateSfx : Gen buffer error 0x%08X", error);
00306     }
00307 
00308     // Copy data to AL buffer
00309     alGetError ();
00310     alBufferData (buffer, format, data, size, freq);
00311     if ((error = alGetError()) != AL_NO_ERROR) {
00312       gtfo ("CAudioManager::CreateSfx : BufferData error 0x%08X", error);
00313     }
00314 
00315     delete[] data;
00316 
00317 #endif // HAVE_OPENAL
00318 
00319     // Deallocate WAV buffer and close pod file
00320     delete[] wavBuffer;
00321     pclose (p);
00322   
00323     // Allocate new CSoundEffect and add to the cache
00324     CSoundEffect *sfx = new CSoundEffect;
00325     sfx->bound = false;
00326 
00327 #ifdef HAVE_OPENAL
00328 
00329     sfx->buffer = buffer;
00330     sfx->source = 0;
00331 
00332 #endif // HAVE_OPENAL
00333 
00334     strcpy (sfx->wavfile, wavfile);
00335   
00336     // For now, unique "name" for the sound effect is the pointer to the
00337     // SSoundEffect object...it is redundant to store this here but the
00338     // unique naming scheme may change in the future
00339     rc = sfx->name = (int)sfx;
00340 
00341     cache[wavfile] = sfx;
00342   }
00343 
00344   return rc;
00345 }
00346 
00347 
00348 //
00349 // GetSoundEffect
00350 //
00351 // Get a pointer to the sound effect with the specified name.  If the name
00352 // is not valid, a NULL pointer is returned
00353 //
00354 CSoundEffect *CAudioManager::GetSoundEffect (int name)
00355 {
00356   CSoundEffect *rc = NULL;
00357 
00358   map<string,CSoundEffect*>::iterator iter;
00359   for (iter = cache.begin(); iter != cache.end() && (rc == NULL); iter++) {
00360     CSoundEffect *sfx = iter->second;
00361     if (sfx->name == name) {
00362       rc = sfx;
00363     }
00364   }
00365 
00366   return rc;
00367 }
00368 
00369 //
00370 //  GetFirstSoundEffectByUserData
00371 // 
00372 //  Returns the first sound effect matching the specified user data.  If
00373 //  no cached sound effects match the user data then a NULL pointer is
00374 //  returned.
00375 //
00376 
00377 CSoundEffect *CAudioManager::GetFirstSoundEffectByUserData
00378                              (int user1, int user2)
00379 {
00380   CSoundEffect *rc = NULL;
00381 
00382   for (iterUserData = cache.begin();
00383        iterUserData != cache.end() && (rc == NULL);
00384        iterUserData++)
00385   {
00386     CSoundEffect *sfx = iterUserData->second;
00387     if ((sfx->user1 == user1) && (sfx->user2 == user2)) {
00388       rc = sfx;
00389     }
00390   }
00391 
00392   return rc;
00393 }
00394 
00395 //
00396 //  GetNextSoundEffectByUserData
00397 // 
00398 //  Returns the next sound effect matching the specified user data.
00399 //  The method GetFirstSoundEffectByUserData should have been called
00400 //  previously with the same arguments to ensure accurate search results.
00401 //  A NULL pointer is returned if no additional sound effects are found
00402 //  with the specified user data.
00403 //
00404 CSoundEffect *CAudioManager::GetNextSoundEffectByUserData
00405                              (int user1, int user2)
00406 {
00407   CSoundEffect *rc = NULL;
00408 
00409   for (;
00410        iterUserData != cache.end() && (rc == NULL);
00411        iterUserData++)
00412   {
00413     CSoundEffect *sfx = iterUserData->second;
00414     if ((sfx->user1 == user1) && (sfx->user2 == user2)) {
00415       rc = sfx;
00416     }
00417   }
00418 
00419   return rc;
00420 }
00421 
00422 //
00423 // IsValidSfx
00424 //
00425 // Checks that the supplied name refers to a valid sound effect
00426 //
00427 int CAudioManager::IsValidSfx (int name)
00428 {
00429   return (GetSoundEffect (name) != NULL);
00430 }
00431 
00432 
00433 //
00434 // AssignSfx
00435 //
00436 void CAudioManager::AssignSfx (int source, int buffer)
00437 {
00438 #ifdef HAVE_OPENAL
00439 
00440   ALint error;
00441 
00442   alGetError ();
00443   alSourcei (source, AL_BUFFER, buffer);
00444   if ((error = alGetError()) != AL_NO_ERROR) {
00445     globals->logWarning->Write ("CAudioManager::AssignSfx : Error 0x%08X", error);
00446   }
00447 
00448 #endif // HAVE_OPENAL
00449 }
00450 
00451 
00452 //
00453 // PlaySfx
00454 //
00455 // Plays the specified sound effect immediately in one-shot mode (no looping)
00456 //
00457 int CAudioManager::PlaySfx (int source, int name)
00458 {
00459   CSoundEffect *sfx = GetSoundEffect (name);
00460   if (sfx != NULL) {
00462 
00463 #ifdef HAVE_OPENAL
00464     // If already attached to a source, stop and disconnect
00465     if (sfx->source != 0) {
00466       alSourceStop (sfx->source);
00467       alSourcei (source, AL_BUFFER, 0);
00468     }
00469 
00470     // Assign buffer to the specified source
00471     AssignSfx (source, sfx->buffer);
00472 
00473     // Play the source
00474     alGetError ();
00475     alSourcePlay (source);
00476     if (ALint error = alGetError () != AL_NO_ERROR) {
00477       globals->logWarning->Write ("CAudioManager::PlaySfx : Error 0x%08X", error);
00478     }
00479 #endif // HAVE_OPENAL
00480   }
00481 
00482   return sfx->name;
00483 }
00484 
00485 //
00486 // PlaySfxDelay
00487 //
00488 // Plays the specified sound effect in one-shot mode (no looping) after
00489 // the a delay
00490 //
00491 int CAudioManager::PlaySfxDelay (int source, int name, float delay, float *length)
00492 {
00493   CSoundEffect *sfx = GetSoundEffect (name);
00494   if (sfx != NULL) {
00495 
00496 #ifdef HAVE_OPENAL
00497 
00498     alGetError ();
00499     alSourcePlay (sfx->source);
00500     if (ALint error = alGetError () != AL_NO_ERROR) {
00501       globals->logWarning->Write ("CAudioManager::PlaySfxDelay : Error 0x%08X", error);
00502     }
00503 
00504 #endif // HAVE_OPENAL
00505 
00506   }
00507 
00508   return sfx->name;
00509 }
00510 
00511 //
00512 // StopSfx
00513 //
00514 // Stops the specified sound effect if it is currently playing
00515 //
00516 void CAudioManager::StopSfx (int name)
00517 {
00518   CSoundEffect *sfx = GetSoundEffect (name);
00519   if (sfx != NULL) {
00520 
00521 #ifdef HAVE_OPENAL
00522 
00523     alGetError ();
00524     alSourceStop (sfx->source);
00525     if (ALint error = alGetError () != AL_NO_ERROR) {
00526       globals->logWarning->Write ("CAudioManager::StopSfx : Error 0x%08X", error);
00527     }
00528 
00529     // Detach
00530     alSourcei (sfx->source, AL_BUFFER, 0);
00531 
00532 #endif // HAVE_OPENAL
00533 
00534   }
00535 }
00536 
00537 //
00538 // StopSfxByUserData
00539 //
00540 // Stop playing a sound effect indentified by its user data.  This may
00541 // result in several sound effects being stopped.
00542 //
00543 void CAudioManager::StopSfxByUserData (int user1, int user2)
00544 {
00545   CSoundEffect *sfx = GetFirstSoundEffectByUserData (user1, user2);
00546   while (sfx != NULL) {
00547 
00548 #ifdef HAVE_OPENAL
00549 
00550     alGetError ();
00551     alSourceStop (sfx->source);
00552     if (ALint error = alGetError () != AL_NO_ERROR) {
00553       globals->logWarning->Write ("CAudioManager::StopSfx : Error 0x%08X", error);
00554     }
00555 
00556 #endif // HAVE_OPENAL
00557 
00558     sfx = GetNextSoundEffectByUserData (user1, user2);
00559   }
00560 }
00561 
00562 //
00563 // SetSfxVolume
00564 //
00565 // Set the relative volume of the sound effect.  The volume setting is
00566 // clamped to the range [0.0, 1.0] where 0.0 is fully attenuated (mute)
00567 // and 1.0 is full volume (original unattenuated WAV).
00568 //
00569 void CAudioManager::SetSfxVolume (int name, float volume)
00570 {
00571   // Clamp volume to range [0.0, 1.0]
00572   if (volume < 0.0f) volume = 0.0f;
00573   if (volume > 1.0f) volume = 1.0f;
00574 
00575   CSoundEffect *sfx = GetSoundEffect (name);
00576   if (sfx != NULL) {
00577 
00578 #ifdef HAVE_OPENAL
00579 
00580     alGetError ();
00581     alSourcef (sfx->source, AL_GAIN, volume);
00582     if (ALint error = alGetError () != AL_NO_ERROR) {
00583       globals->logWarning->Write ("CAudioManager::SetSfxVolume : Error 0x%08X", error);
00584     }
00585 
00586 #endif // HAVE_OPENAL
00587 
00588   }
00589 }
00590 
00591 //
00592 // SetSfxPosition (SPosition)
00593 //
00594 // Sets the 3D position of the specified sound effect in Fly! geodetic
00595 // coordinates (lat/lon/alt AGL).  This method only needs to be called
00596 // if the sound effect requires distance attenuation.  By default, all
00597 // sound effects are assumed to be co-located with the listener position.
00598 //
00599 void CAudioManager::SetSfxPosition (int name, SPosition *p)
00600 {
00602   // SetSfxPosition (v);
00603 }
00604 
00605 //
00606 // SetSfxPosition (SVector)
00607 //
00608 // Sets the 3D position of the specified sound effect in cartesian
00609 // coordinates relative to the listener.  The SVector argument specifies
00610 // the distance in feet of the sound effect relative to the listener.
00611 // X-coordinate is positive to the right of the listener
00612 // Y-coordinate is positive above the listener
00613 // Z-coordinate is positive ahead of the listener
00614 //
00615 void CAudioManager::SetSfxPosition (int name, SVector *v)
00616 {
00617   CSoundEffect *sfx = GetSoundEffect (name);
00618   if (sfx != NULL) {
00619 
00620 #ifdef HAVE_OPENAL
00621 
00622     alGetError ();
00623     alSource3f (sfx->source, AL_POSITION, v->x, v->y, v->z);
00624     if (ALint error = alGetError () != AL_NO_ERROR) {
00625       globals->logWarning->Write ("CAudioManager::SetSfxPosition : Error 0x%08X", error);
00626     }
00627 
00628 #endif // HAVE_OPENAL
00629 
00630   }
00631 }
00632 
00633 //
00634 // SetSfxDirection
00635 //
00636 void CAudioManager::SetSfxDirection (int name, SVector *v)
00637 {
00638   CSoundEffect *sfx = GetSoundEffect (name);
00639   if (sfx != NULL) {
00640 
00641 #ifdef HAVE_OPENAL
00642 
00643     alGetError ();
00644     alSource3f (sfx->source, AL_DIRECTION, v->x, v->y, v->z);
00645     if (ALint error = alGetError () != AL_NO_ERROR) {
00646       globals->logWarning->Write ("CAudioManager::SetSfxDirection : Error 0x%08X", error);
00647     }
00648 
00649 #endif // HAVE_OPENAL
00650 
00651   }
00652 }
00653 
00654 //
00655 // SetSfxVelocity
00656 //
00657 // Sets the sound effect velocity relative to the listener.   This method
00658 // only needs to be called if doppler shift is desired on the sound effect.
00659 // By default, all sound effects are assumed to be co-located with the
00660 // listener and therefore have no doppler effect.
00661 //
00662 void CAudioManager::SetSfxVelocity (int name, SVector *v)
00663 {
00665 }
00666 
00667 //
00668 // SetSfxFrequency
00669 //
00670 // Set the relative playback frequency for the sound effect.  The
00671 // frequency scalar (freqScalar) parameter is a multiplier against the
00672 // base sampling rate for the sound effect.  Default is 1.0 (normal
00673 // playback rate), a value of 2.0 would play the sound effect at
00674 // double the rate, 0.5 would play it back at half the rate, etc.
00675 // Frequency rate of less than 0.01 is clamped to 0.01
00676 //
00677 void CAudioManager::SetSfxFrequency (int name, float freqScalar)
00678 {
00679   // Clamp to lower bound of 0.01
00680   if (freqScalar < 0.01) freqScalar = 0.01f;
00681 
00682   CSoundEffect *sfx = GetSoundEffect (name);
00683   if (sfx != NULL) {
00684 
00685 #ifdef HAVE_OPENAL
00686 
00687     alGetError ();
00688     alSourcef (sfx->source, AL_PITCH, freqScalar);
00689     if (ALint error = alGetError () != AL_NO_ERROR) {
00690       globals->logWarning->Write ("CAudioManager::SetSfxFrequency : Error 0x%08X", error);
00691     }
00692 
00693 #endif // HAVE_OPENAL
00694 
00695   }
00696 }
00697 
00698 //
00699 // SetUserData
00700 //
00701 // Every sound effect can optionally have two pieces of user data associated
00702 // with it (for example, a subsystem unique tag and a unit number).  This
00703 // user data can then be used to control playback of all sound effects
00704 // with the matching tag values.
00705 //
00706 void CAudioManager::SetSfxUserData (int name, int user1, int user2)
00707 {
00708   CSoundEffect *sfx = GetSoundEffect (name);
00709   if (sfx != NULL) {
00710     sfx->user1 = user1;
00711     sfx->user2 = user2;
00712   }
00713 }
SourceForge.net Logo Documentation generated by doxygen