Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/orxonox/sound/SoundManager.cc @ 7183

Last change on this file since 7183 was 7174, checked in by rgrieder, 14 years ago

Only catch exceptions you actually expect. And rethrow unknown exceptions ("…" can also catch internal Microsoft exceptions like floating point exception).

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