Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/archive/tutorial2/src/orxonox/sound/SoundManager.cc @ 12373

Last change on this file since 12373 was 8903, checked in by baermatt, 13 years ago

Just excluding alutInit and alutExit on Mac OS X, because it causes an internal warning.

  • Property svn:eol-style set to native
File size: 24.1 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *       Erwin 'vaiursch' Herrsche
24 *       Kevin Young
25 *       Reto Grieder
26 *   Co-authors:
27 *      ...
28 *
29 */
30
31#include "SoundManager.h"
32
33#include <utility>
34#include <alut.h>
35#include <loki/ScopeGuard.h>
36
37#include "util/Exception.h"
38#include "util/Math.h"
39#include "util/Clock.h"
40#include "util/ScopedSingletonManager.h"
41#include "core/ConfigValueIncludes.h"
42#include "core/CoreIncludes.h"
43#include "core/GameMode.h"
44#include "core/Resource.h"
45#include "SoundBuffer.h"
46#include "BaseSound.h"
47#include "AmbientSound.h"
48#include "WorldSound.h"
49
50namespace orxonox
51{
52    ManageScopedSingleton(SoundManager, ScopeID::Graphics, true);
53
54    std::string SoundManager::getALErrorString(ALenum code)
55    {
56        switch (code)
57        {
58        case AL_NO_ERROR:          return "No error";
59        case AL_INVALID_NAME:      return "Invalid AL parameter name";
60        case AL_INVALID_ENUM:      return "Invalid AL enum";
61        case AL_INVALID_VALUE:     return "Invalid AL value";
62        case AL_INVALID_OPERATION: return "Invalid AL operation";
63        case AL_OUT_OF_MEMORY:     return "AL reports out of memory";
64        default:                   return "Unknown AL error";
65        }
66    }
67
68    SoundManager::SoundManager()
69        : effectsPoolSize_(0)
70    {
71        RegisterRootObject(SoundManager);
72
73        orxout(user_status) << "Loading sound" << endl;
74
75        this->bDestructorCalled_ = false;
76         
77        // See whether we even want to load
78        bool bDisableSound_ = false;
79        SetConfigValue(bDisableSound_, false);
80        if (bDisableSound_)
81            ThrowException(InitialisationAborted, "Sound: Not loading at all");
82#if !defined(ORXONOX_PLATFORM_APPLE)
83        if (!alutInitWithoutContext(NULL, NULL))
84            ThrowException(InitialisationFailed, "Sound Error: ALUT initialisation failed: " << alutGetErrorString(alutGetError()));
85        Loki::ScopeGuard alutExitGuard = Loki::MakeGuard(&alutExit);
86#endif
87
88/*
89        // Get list of available sound devices and display them
90        const char* devices = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
91        char* device = new char[strlen(devices)+1];
92        strcpy(device, devices);
93        std::string renderDevice;
94        SetConfigValue(renderDevice, std::string(device)).description("Sound device used for rendering");
95        orxout(verbose, context::sound) << "Sound: Available devices: ";
96        while (true)
97        {
98            this->deviceNames_.push_back(devices);
99            orxout(verbose, context::sound) << '"' << devices << "\", ";
100            devices += strlen(devices) + 1;
101            if (*devices == '\0')
102                break;
103        }
104        orxout(verbose, context::sound) << endl;
105
106        // Open the selected device
107        orxout(internal_info, context::sound) << "Sound: Opening device \"" << renderDevice << '\' << endl;
108        this->device_ = alcOpenDevice(renderDevice.c_str());
109*/
110        this->device_ = alcOpenDevice(NULL);
111        if (this->device_ == NULL)
112            ThrowException(InitialisationFailed, "Sound Error: Could not open sound device.");
113        Loki::ScopeGuard closeDeviceGuard = Loki::MakeGuard(&alcCloseDevice, this->device_);
114
115        // Create sound context and make it the currently used one
116        this->context_ = alcCreateContext(this->device_, NULL);
117        if (this->context_ == NULL)
118            ThrowException(InitialisationFailed, "Sound Error: Could not create ALC context");
119        Loki::ScopeGuard desroyContextGuard = Loki::MakeGuard(&alcDestroyContext, this->context_);
120        if (!alcMakeContextCurrent(this->context_))
121            ThrowException(InitialisationFailed, "Sound Error: Could not use ALC context");
122
123        GameMode::setPlaysSound(true);
124        Loki::ScopeGuard resetPlaysSoundGuard = Loki::MakeGuard(&GameMode::setPlaysSound, false);
125
126        // Get some information about the sound
127        if (const char* version = alGetString(AL_VERSION))
128            orxout(internal_info, context::sound) << "Sound: --- OpenAL Version: " << version << endl;
129        if (const char* vendor = alGetString(AL_VENDOR))
130            orxout(internal_info, context::sound) << "Sound: --- OpenAL Vendor : " << vendor << endl;
131        if (const char* types = alutGetMIMETypes(ALUT_LOADER_BUFFER))
132            orxout(internal_info, context::sound) << "Sound: --- Supported MIME Types: " << types << endl;
133        else
134            orxout(internal_warning, context::sound) << "MIME Type retrieval failed: " << alutGetErrorString(alutGetError()) << endl;
135
136        this->mute_[SoundType::All]     = 1.0f;
137        this->mute_[SoundType::Music]   = 1.0f;
138        this->mute_[SoundType::Effects] = 1.0f;
139
140        this->setConfigValues();
141
142        // Try to get exactly one source
143        ALuint source;
144        alGenSources(1, &source);
145        if (!alGetError() && alIsSource(source))
146            this->availableSoundSources_.push_back(source);
147        else
148            ThrowException(InitialisationFailed, "Sound Error: Could not create even a single source");
149        // Create a few initial sources
150        this->createSoundSources(this->minSources_ - 1);
151
152        // Disarm guards
153#if !defined(ORXONOX_PLATFORM_APPLE)
154        alutExitGuard.Dismiss();
155#endif
156        closeDeviceGuard.Dismiss();
157        desroyContextGuard.Dismiss();
158        resetPlaysSoundGuard.Dismiss();
159
160        orxout(internal_status, context::sound) << "Sound: Initialisation complete" << endl;
161    }
162
163    SoundManager::~SoundManager()
164    {
165        // Erase fade lists because of the smart pointers
166        this->bDestructorCalled_ = true;
167        this->fadeInList_.clear();
168        this->fadeOutList_.clear();
169
170        // If there are still used buffers around, well, that's just very bad...
171        if (this->soundBuffers_.size() != this->effectsPool_.size())
172            orxout(internal_error, context::sound) << "Some sound buffers are still in use but OpenAL is about to shut down. Fix this!" << endl;
173        // Empty buffer pool and buffer list
174        this->effectsPool_.clear();
175        this->soundBuffers_.clear();
176
177        // There should not be any sources in use anymore
178        if (!this->usedSoundSources_.empty())
179            orxout(internal_error, context::sound) << "Some sound sources are still in use but OpenAL is about to shut down. Fix this!" << endl;
180        while (!this->availableSoundSources_.empty())
181        {
182            alDeleteSources(1, &this->availableSoundSources_.back());
183            this->availableSoundSources_.pop_back();
184        }
185
186        GameMode::setPlaysSound(false);
187
188        // Relieve context to destroy it
189        if (!alcMakeContextCurrent(NULL))
190            orxout(internal_error, context::sound) << "Could not unset ALC context" << endl;
191        alcDestroyContext(this->context_);
192        if (ALCenum error = alcGetError(this->device_))
193        {
194            if (error == AL_INVALID_OPERATION)
195                orxout(internal_error, context::sound) << "Could not destroy ALC context because it is the current one" << endl;
196            else
197                orxout(internal_error, context::sound) << "Could not destroy ALC context because it is invalid" << endl;
198        }
199#ifdef AL_VERSION_1_1
200        if (!alcCloseDevice(this->device_))
201            orxout(internal_error, context::sound) << "Could not destroy ALC device. This might be because there are still buffers in use!" << endl;
202#else
203        alcCloseDevice(this->device_);
204#endif
205#if !defined(ORXONOX_PLATFORM_APPLE)
206        if (!alutExit())
207            orxout(internal_error, context::sound) << "Closing ALUT failed: " << alutGetErrorString(alutGetError()) << endl;
208#endif
209    }
210
211    void SoundManager::setConfigValues()
212    {
213        SetConfigValue(crossFadeStep_, 0.2f)
214            .description("Determines how fast sounds should fade, per second.")
215            .callback(this, &SoundManager::checkFadeStepValidity);
216
217        SetConfigValueAlias(volume_[SoundType::All], "soundVolume_", 1.0f)
218            .description("Defines the overall volume.")
219            .callback(this, &SoundManager::checkSoundVolumeValidity);
220        SetConfigValueAlias(volume_[SoundType::Music], "ambientVolume_", 1.0f)
221            .description("Defines the ambient volume.")
222            .callback(this, &SoundManager::checkAmbientVolumeValidity);
223        SetConfigValueAlias(volume_[SoundType::Effects], "effectsVolume_", 1.0f)
224            .description("Defines the effects volume.")
225            .callback(this, &SoundManager::checkEffectsVolumeValidity);
226
227        SetConfigValue(minSources_, 16)
228            .description("Minimum number of sources being generated (if possible)");
229        SetConfigValue(maxSources_, 1024)
230            .description("Maximum number of sources to be made available");
231    }
232
233    void SoundManager::preUpdate(const Clock& time)
234    {
235        this->processCrossFading(time.getDeltaTime());
236
237        // Check whether a sound object has stopped playing
238        for (unsigned int i = 0; i < this->usedSoundSources_.size(); ++i)
239        {
240            ALint state;
241            alGetSourcei(this->usedSoundSources_[i].first, AL_SOURCE_STATE, &state);
242            if (state == AL_STOPPED)
243            {
244                if (this->usedSoundSources_[i].second->stop()) // if stop() returns true, the sound source was removed, thus decrement the array index
245                    --i;
246            }
247        }
248    }
249
250    void SoundManager::checkFadeStepValidity()
251    {
252        if (crossFadeStep_ <= 0.0 || crossFadeStep_ >= 1.0 )
253        {
254            orxout(internal_warning, context::sound) << "Fade step out of range, ignoring change." << endl;
255            ResetConfigValue(crossFadeStep_);
256        }
257    }
258
259    void SoundManager::checkVolumeValidity(SoundType::Value type)
260    {
261        float clampedVolume = clamp(this->volume_[type], 0.0f, 1.0f);
262        if (clampedVolume != this->volume_[type])
263            orxout(internal_warning, context::sound) << "Volume setting (" << type << ") out of range, clamping." << endl;
264        this->updateVolume(type);
265    }
266
267    void SoundManager::setVolume(float vol, SoundType::Value type)
268    {
269        if (type < 0 || type > SoundType::Effects)
270            return;
271        this->volume_[type] = vol;
272        this->checkVolumeValidity(type);
273    }
274
275    float SoundManager::getVolume(SoundType::Value type)
276    {
277        if (type < 0 || type > SoundType::Effects)
278            return 0.0f;
279        return this->volume_[type];
280    }
281
282    float SoundManager::getRealVolume(SoundType::Value type)
283    {
284        if (type != SoundType::Music && type != SoundType::Effects)
285            return 0.0f;
286        return this->volume_[SoundType::All] * this->mute_[SoundType::All] * this->volume_[type] * this->mute_[type];
287    }
288
289    void SoundManager::updateVolume(SoundType::Value type)
290    {
291        switch(type)
292        {
293        case SoundType::All:
294            for (ObjectList<BaseSound>::iterator it = ObjectList<BaseSound>::begin(); it != ObjectList<BaseSound>::end(); ++it)
295                (*it)->updateVolume();
296            break;
297        case SoundType::Music:
298            for (ObjectList<AmbientSound>::iterator it = ObjectList<AmbientSound>::begin(); it != ObjectList<AmbientSound>::end(); ++it)
299                (*it)->updateVolume();
300            break;
301        case SoundType::Effects:
302            for (ObjectList<WorldSound>::iterator it = ObjectList<WorldSound>::begin(); it != ObjectList<WorldSound>::end(); ++it)
303                (*it)->updateVolume();
304            break;
305        default:
306            assert(false);
307        }
308    }
309
310    void SoundManager::toggleMute(SoundType::Value type)
311    {
312        if (type < 0 || type > SoundType::Effects)
313            return;
314        this->mute_[type] = (this->mute_[type] == 0) ? 1.0f : 0.0f;
315        this->updateVolume(type);
316    }
317
318    bool SoundManager::getMute(SoundType::Value type)
319    {
320        if (type < 0 || type > SoundType::Effects)
321            return true;
322        return (this->mute_[type] == 0);
323    }
324
325    void SoundManager::setListenerPosition(const Vector3& position)
326    {
327        alListener3f(AL_POSITION, position.x, position.y, position.z);
328        ALenum error = alGetError();
329        if (error == AL_INVALID_VALUE)
330            // @TODO: Follow this constantly appearing, nerve-racking warning
331            orxout(internal_error, context::sound) << "OpenAL: Invalid listener position" << endl;
332    }
333
334    void SoundManager::setListenerOrientation(const Quaternion& orientation)
335    {
336        // update listener orientation
337        const Vector3& direction = -orientation.zAxis();
338        const Vector3& up = orientation.yAxis();
339
340        ALfloat orient[6] = { direction.x, direction.y, direction.z, up.x, up.y, up.z };
341
342        alListenerfv(AL_ORIENTATION, orient);
343        ALenum error = alGetError();
344        if (error == AL_INVALID_VALUE)
345            orxout(internal_error, context::sound) << "OpenAL: Invalid listener orientation" << endl;
346    }
347
348    void SoundManager::registerAmbientSound(AmbientSound* newAmbient)
349    {
350        if (newAmbient != NULL && !this->bDestructorCalled_)
351        {
352            for (AmbientList::const_iterator it = this->ambientSounds_.begin(); it != this->ambientSounds_.end(); ++it)
353            {
354                if (it->first == newAmbient)
355                {
356                    orxout(internal_warning, context::sound) << "Will not play an AmbientSound twice." << endl;
357                    return;
358                }
359            }
360
361            if (!this->ambientSounds_.empty())
362            {
363                this->fadeOut(ambientSounds_.front().first);
364            }
365            this->ambientSounds_.push_front(std::make_pair(newAmbient, false));
366            newAmbient->doPlay();
367            this->fadeIn(newAmbient);
368        }
369    }
370
371    void SoundManager::unregisterAmbientSound(AmbientSound* oldAmbient)
372    {
373        if (oldAmbient == NULL || ambientSounds_.empty() || this->bDestructorCalled_)
374            return;
375
376        if (this->ambientSounds_.front().first == oldAmbient)
377        {
378            this->fadeOut(oldAmbient);
379            this->ambientSounds_.pop_front();
380            if (!this->ambientSounds_.empty())
381            {
382                if (!this->ambientSounds_.front().second) // Not paused before
383                {
384                    this->ambientSounds_.front().first->doPlay();
385                }
386                this->fadeIn(this->ambientSounds_.front().first);
387            }
388        }
389        else
390        {
391            for (AmbientList::iterator it = this->ambientSounds_.begin(); it != this->ambientSounds_.end(); ++it)
392            {
393                if (it->first == oldAmbient)
394                {
395                    this->fadeOut(oldAmbient);
396                    this->ambientSounds_.erase(it);
397                    break;
398                }
399            }
400        }
401    }
402
403    void SoundManager::pauseAmbientSound(AmbientSound* ambient)
404    {
405        if (ambient != NULL)
406        {
407            for (AmbientList::iterator it = this->ambientSounds_.begin(); it != this->ambientSounds_.end(); ++it)
408            {
409                if (it->first == ambient)
410                {
411                    it->second = true;
412                    this->fadeOut(it->first);
413                    return;
414                }
415            }
416        }
417    }
418
419    void SoundManager::fadeIn(const SmartPtr<AmbientSound>& sound)
420    {
421        // If we're already fading out --> remove that
422        for (std::list<SmartPtr<AmbientSound> >::iterator it = this->fadeOutList_.begin(); it != this->fadeOutList_.end(); it++)
423        {
424            if (*it == sound)
425            {
426                this->fadeOutList_.erase(it);
427                break;
428            }
429        }
430        // No duplicate entries
431        if (std::find(this->fadeInList_.begin(), this->fadeInList_.end(), sound) == this->fadeInList_.end())
432            this->fadeInList_.push_back(sound);
433    }
434
435    void SoundManager::fadeOut(const SmartPtr<AmbientSound>& sound)
436    {
437        // If we're already fading in --> remove that
438        for (std::list<SmartPtr<AmbientSound> >::iterator it = this->fadeInList_.begin(); it != this->fadeInList_.end(); it++)
439        {
440            if (*it == sound)
441            {
442                this->fadeInList_.erase(it);
443                break;
444            }
445        }
446        // No duplicate entries
447        if (std::find(this->fadeOutList_.begin(), this->fadeOutList_.end(), sound) == this->fadeOutList_.end())
448            this->fadeOutList_.push_back(sound);
449    }
450
451    void SoundManager::processCrossFading(float dt)
452    {
453
454        // Hacky solution to the fade delay while loading a level.
455        if(dt > 0.2)
456        {
457            return;
458        }
459
460        // FADE IN
461        for (std::list<SmartPtr<AmbientSound> >::iterator it= this->fadeInList_.begin(); it != this->fadeInList_.end(); )
462        {
463            if ((*it)->getVolume() + this->crossFadeStep_*dt > 1.0f)
464            {
465                (*it)->setVolume(1.0f);
466                this->fadeInList_.erase(it++);
467            }
468            else
469            {
470                (*it)->setVolume((*it)->getVolume() + this->crossFadeStep_*dt);
471                ++it;
472            }
473        }
474
475        // FADE OUT
476        for (std::list<SmartPtr<AmbientSound> >::iterator it = this->fadeOutList_.begin(); it != this->fadeOutList_.end(); )
477        {
478            if ((*it)->getVolume() - this->crossFadeStep_*dt < 0.0f)
479            {
480                (*it)->setVolume(0.0f);
481
482                // If sound is in the ambient list --> pause
483                for (AmbientList::const_iterator it2 = this->ambientSounds_.begin(); it2 != this->ambientSounds_.end(); ++it2)
484                {
485                    if (it2->first == *it)
486                    {
487                        (*it)->doPause();
488                        break;
489                    }
490                }
491                // If not pause (by loop above for instance) --> stop
492                if (!(*it)->isPaused())
493                    (*it)->doStop();
494
495                this->fadeOutList_.erase(it++);
496            }
497            else
498            {
499                (*it)->setVolume((*it)->getVolume() - this->crossFadeStep_*dt);
500                ++it;
501            }
502        }
503    }
504
505    shared_ptr<SoundBuffer> SoundManager::getSoundBuffer(const std::string& filename)
506    {
507        shared_ptr<SoundBuffer> buffer;
508        // Check active or pooled buffers
509        SoundBufferMap::const_iterator it = this->soundBuffers_.find(filename);
510        if (it != this->soundBuffers_.end())
511        {
512            buffer = it->second;
513
514            // Remove from effects pool if not active used before
515            if (buffer->poolIterator_ != this->effectsPool_.end())
516            {
517                this->effectsPoolSize_ -= buffer->getSize();
518                this->effectsPool_.erase(buffer->poolIterator_);
519                buffer->poolIterator_ = this->effectsPool_.end();
520            }
521        }
522        else
523        {
524            try
525            {
526                buffer.reset(new SoundBuffer(filename, this->effectsPool_.end()));
527            }
528            catch (const std::exception& ex)
529            {
530                orxout(internal_error, context::sound) << ex.what() << endl;
531                return buffer;
532            }
533            this->soundBuffers_[filename] = buffer;
534        }
535        return buffer;
536    }
537
538    void SoundManager::releaseSoundBuffer(const shared_ptr<SoundBuffer>& buffer, bool bPoolBuffer)
539    {
540        // Check if others are still using the buffer
541        if (buffer.use_count() != 2)
542            return;
543        SoundBufferMap::iterator it = this->soundBuffers_.find(buffer->getFilename());
544        if (it != this->soundBuffers_.end())
545        {
546            if (bPoolBuffer)
547            {
548                // Pool already too large?
549                while (this->effectsPoolSize_ + it->second->getSize() > this->maxEffectsPoolSize_s && !this->effectsPool_.empty())
550                {
551                    shared_ptr<SoundBuffer> bufferDel = this->effectsPool_.back();
552                    this->effectsPoolSize_ -= bufferDel->getSize();
553                    bufferDel->poolIterator_ = this->effectsPool_.end();
554                    this->effectsPool_.pop_back();
555                    // Remove from buffer map too
556                    SoundBufferMap::iterator itDel = this->soundBuffers_.find(bufferDel->getFilename());
557                    if (itDel != this->soundBuffers_.end())
558                        this->soundBuffers_.erase(itDel);
559                }
560                // Put buffer into the pool
561                this->effectsPoolSize_ += it->second->getSize();
562                this->effectsPool_.push_front(it->second);
563                it->second->poolIterator_ = this->effectsPool_.begin();
564            }
565            else
566                this->soundBuffers_.erase(it);
567        }
568    }
569
570    ALuint SoundManager::getSoundSource(BaseSound* object)
571    {
572        if (!this->availableSoundSources_.empty())
573        {
574            ALuint source = this->availableSoundSources_.back();
575            this->availableSoundSources_.pop_back();
576            this->usedSoundSources_.push_back(std::make_pair(source, object));
577            return source;
578        }
579        else
580        {
581            if (this->usedSoundSources_.size() < this->maxSources_)
582            {
583                ALuint source;
584                alGenSources(1, &source);
585                // Try to create new sources (50% more, but at least one)
586                if (alIsSource(source) && !alGetError())
587                {
588                    this->usedSoundSources_.push_back(std::make_pair(source, object));
589                    return source;
590                }
591            }
592            // Return no source ID
593            ALuint source = 123456789;
594            while (alIsSource(++source));
595            return source;
596        }
597    }
598
599    void SoundManager::releaseSoundSource(ALuint source)
600    {
601#ifndef NDEBUG
602        for (std::vector<ALuint>::const_iterator it = this->availableSoundSources_.begin(); it != this->availableSoundSources_.end(); ++it)
603            assert((*it) != source);
604#endif
605        this->availableSoundSources_.push_back(source);
606        for (std::vector<std::pair<ALuint, BaseSound*> >::iterator it = this->usedSoundSources_.begin();
607            it != this->usedSoundSources_.end(); ++it)
608        {
609            if (it->first == source)
610            {
611                this->usedSoundSources_.erase(it);
612                break;
613            }
614        }
615        int used = std::max((unsigned int)(this->usedSoundSources_.size()), this->minSources_);
616        // Subtract those we added in the statement above trough std::max
617        int available = (int)this->availableSoundSources_.size() - (used - (int)this->usedSoundSources_.size());
618        // Delete sources again to free resources if appropriate (more than 50% more available than used)
619        int toDelete = available - used / 2;
620        while (toDelete-- > 0)
621        {
622            alDeleteSources(1, &this->availableSoundSources_.back());
623            if (alGetError())
624                orxout(internal_error, context::sound) << "Failed to delete a source --> lost forever" << endl;
625            this->availableSoundSources_.pop_back();
626        }
627    }
628
629    unsigned int SoundManager::createSoundSources(unsigned int n)
630    {
631        unsigned int count = this->availableSoundSources_.size() + this->usedSoundSources_.size();
632        while (count < this->maxSources_ && count <= n)
633        {
634            ALuint source;
635            alGenSources(1, &source);
636            if (alIsSource(source) && !alGetError())
637                this->availableSoundSources_.push_back(source);
638            else
639                break;
640            ++count;
641        }
642        return count - this->availableSoundSources_.size() - this->usedSoundSources_.size();
643    }
644}
Note: See TracBrowser for help on using the repository browser.