Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/core7/src/orxonox/sound/SoundManager.cc @ 10426

Last change on this file since 10426 was 10413, checked in by landauf, 10 years ago

use the generic UpdateListener interface to receive calls to preUpdate() and postUpdate() instead of limiting this functionality to singletons.

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