]> git.lizzy.rs Git - minetest.git/blob - src/client/sound_openal.cpp
Clean up sound_fade (#10119)
[minetest.git] / src / client / sound_openal.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 OpenAL support based on work by:
5 Copyright (C) 2011 Sebastian 'Bahamada' Rühl
6 Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
7 Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License along
20 with this program; ifnot, write to the Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 #include "sound_openal.h"
25
26 #if defined(_WIN32)
27         #include <al.h>
28         #include <alc.h>
29         //#include <alext.h>
30 #elif defined(__APPLE__)
31         #include <OpenAL/al.h>
32         #include <OpenAL/alc.h>
33         //#include <OpenAL/alext.h>
34 #else
35         #include <AL/al.h>
36         #include <AL/alc.h>
37         #include <AL/alext.h>
38 #endif
39 #include <cmath>
40 #include <vorbis/vorbisfile.h>
41 #include <cassert>
42 #include "log.h"
43 #include "util/numeric.h" // myrand()
44 #include "porting.h"
45 #include <vector>
46 #include <fstream>
47 #include <unordered_map>
48 #include <unordered_set>
49
50 #define BUFFER_SIZE 30000
51
52 std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
53
54 typedef std::unique_ptr<ALCdevice, void (*)(ALCdevice *p)> unique_ptr_alcdevice;
55 typedef std::unique_ptr<ALCcontext, void(*)(ALCcontext *p)> unique_ptr_alccontext;
56
57 static void delete_alcdevice(ALCdevice *p)
58 {
59         if (p)
60                 alcCloseDevice(p);
61 }
62
63 static void delete_alccontext(ALCcontext *p)
64 {
65         if (p) {
66                 alcMakeContextCurrent(nullptr);
67                 alcDestroyContext(p);
68         }
69 }
70
71 static const char *alErrorString(ALenum err)
72 {
73         switch (err) {
74         case AL_NO_ERROR:
75                 return "no error";
76         case AL_INVALID_NAME:
77                 return "invalid name";
78         case AL_INVALID_ENUM:
79                 return "invalid enum";
80         case AL_INVALID_VALUE:
81                 return "invalid value";
82         case AL_INVALID_OPERATION:
83                 return "invalid operation";
84         case AL_OUT_OF_MEMORY:
85                 return "out of memory";
86         default:
87                 return "<unknown OpenAL error>";
88         }
89 }
90
91 static ALenum warn_if_error(ALenum err, const char *desc)
92 {
93         if(err == AL_NO_ERROR)
94                 return err;
95         warningstream<<desc<<": "<<alErrorString(err)<<std::endl;
96         return err;
97 }
98
99 void f3_set(ALfloat *f3, v3f v)
100 {
101         f3[0] = v.X;
102         f3[1] = v.Y;
103         f3[2] = v.Z;
104 }
105
106 struct SoundBuffer
107 {
108         ALenum format;
109         ALsizei freq;
110         ALuint buffer_id;
111         std::vector<char> buffer;
112 };
113
114 SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile,
115                 const std::string &filename_for_logging)
116 {
117         int endian = 0; // 0 for Little-Endian, 1 for Big-Endian
118         int bitStream;
119         long bytes;
120         char array[BUFFER_SIZE]; // Local fixed size array
121         vorbis_info *pInfo;
122
123         SoundBuffer *snd = new SoundBuffer;
124
125         // Get some information about the OGG file
126         pInfo = ov_info(oggFile, -1);
127
128         // Check the number of channels... always use 16-bit samples
129         if(pInfo->channels == 1)
130                 snd->format = AL_FORMAT_MONO16;
131         else
132                 snd->format = AL_FORMAT_STEREO16;
133
134         // The frequency of the sampling rate
135         snd->freq = pInfo->rate;
136
137         // Keep reading until all is read
138         do
139         {
140                 // Read up to a buffer's worth of decoded sound data
141                 bytes = ov_read(oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream);
142
143                 if(bytes < 0)
144                 {
145                         ov_clear(oggFile);
146                         infostream << "Audio: Error decoding "
147                                 << filename_for_logging << std::endl;
148                         delete snd;
149                         return nullptr;
150                 }
151
152                 // Append to end of buffer
153                 snd->buffer.insert(snd->buffer.end(), array, array + bytes);
154         } while (bytes > 0);
155
156         alGenBuffers(1, &snd->buffer_id);
157         alBufferData(snd->buffer_id, snd->format,
158                         &(snd->buffer[0]), snd->buffer.size(),
159                         snd->freq);
160
161         ALenum error = alGetError();
162
163         if(error != AL_NO_ERROR){
164                 infostream << "Audio: OpenAL error: " << alErrorString(error)
165                                 << "preparing sound buffer" << std::endl;
166         }
167
168         //infostream << "Audio file "
169         //      << filename_for_logging << " loaded" << std::endl;
170
171         // Clean up!
172         ov_clear(oggFile);
173
174         return snd;
175 }
176
177 SoundBuffer *load_ogg_from_file(const std::string &path)
178 {
179         OggVorbis_File oggFile;
180
181         // Try opening the given file.
182         // This requires libvorbis >= 1.3.2, as
183         // previous versions expect a non-const char *
184         if (ov_fopen(path.c_str(), &oggFile) != 0) {
185                 infostream << "Audio: Error opening " << path
186                         << " for decoding" << std::endl;
187                 return nullptr;
188         }
189
190         return load_opened_ogg_file(&oggFile, path);
191 }
192
193 struct BufferSource {
194         const char *buf;
195         size_t cur_offset;
196         size_t len;
197 };
198
199 size_t buffer_sound_read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
200 {
201         BufferSource *s = (BufferSource *)datasource;
202         size_t copied_size = MYMIN(s->len - s->cur_offset, size);
203         memcpy(ptr, s->buf + s->cur_offset, copied_size);
204         s->cur_offset += copied_size;
205         return copied_size;
206 }
207
208 int buffer_sound_seek_func(void *datasource, ogg_int64_t offset, int whence)
209 {
210         BufferSource *s = (BufferSource *)datasource;
211         if (whence == SEEK_SET) {
212                 if (offset < 0 || (size_t)MYMAX(offset, 0) >= s->len) {
213                         // offset out of bounds
214                         return -1;
215                 }
216                 s->cur_offset = offset;
217                 return 0;
218         } else if (whence == SEEK_CUR) {
219                 if ((size_t)MYMIN(-offset, 0) > s->cur_offset
220                                 || s->cur_offset + offset > s->len) {
221                         // offset out of bounds
222                         return -1;
223                 }
224                 s->cur_offset += offset;
225                 return 0;
226         }
227         // invalid whence param (SEEK_END doesn't have to be supported)
228         return -1;
229 }
230
231 long BufferSourceell_func(void *datasource)
232 {
233         BufferSource *s = (BufferSource *)datasource;
234         return s->cur_offset;
235 }
236
237 static ov_callbacks g_buffer_ov_callbacks = {
238         &buffer_sound_read_func,
239         &buffer_sound_seek_func,
240         nullptr,
241         &BufferSourceell_func
242 };
243
244 SoundBuffer *load_ogg_from_buffer(const std::string &buf, const std::string &id_for_log)
245 {
246         OggVorbis_File oggFile;
247
248         BufferSource s;
249         s.buf = buf.c_str();
250         s.cur_offset = 0;
251         s.len = buf.size();
252
253         if (ov_open_callbacks(&s, &oggFile, nullptr, 0, g_buffer_ov_callbacks) != 0) {
254                 infostream << "Audio: Error opening " << id_for_log
255                         << " for decoding" << std::endl;
256                 return nullptr;
257         }
258
259         return load_opened_ogg_file(&oggFile, id_for_log);
260 }
261
262 struct PlayingSound
263 {
264         ALuint source_id;
265         bool loop;
266 };
267
268 class SoundManagerSingleton
269 {
270 public:
271         unique_ptr_alcdevice  m_device;
272         unique_ptr_alccontext m_context;
273 public:
274         SoundManagerSingleton() :
275                 m_device(nullptr, delete_alcdevice),
276                 m_context(nullptr, delete_alccontext)
277         {
278         }
279
280         bool init()
281         {
282                 if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) {
283                         errorstream << "Audio: Global Initialization: Failed to open device" << std::endl;
284                         return false;
285                 }
286
287                 if (!(m_context = unique_ptr_alccontext(
288                                 alcCreateContext(m_device.get(), nullptr), delete_alccontext))) {
289                         errorstream << "Audio: Global Initialization: Failed to create context" << std::endl;
290                         return false;
291                 }
292
293                 if (!alcMakeContextCurrent(m_context.get())) {
294                         errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl;
295                         return false;
296                 }
297
298                 alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
299
300                 if (alGetError() != AL_NO_ERROR) {
301                         errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl;
302                         return false;
303                 }
304
305                 infostream << "Audio: Global Initialized: OpenAL " << alGetString(AL_VERSION)
306                         << ", using " << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER)
307                         << std::endl;
308
309                 return true;
310         }
311
312         ~SoundManagerSingleton()
313         {
314                 infostream << "Audio: Global Deinitialized." << std::endl;
315         }
316 };
317
318 class OpenALSoundManager: public ISoundManager
319 {
320 private:
321         OnDemandSoundFetcher *m_fetcher;
322         ALCdevice *m_device;
323         ALCcontext *m_context;
324         int m_next_id;
325         std::unordered_map<std::string, std::vector<SoundBuffer*>> m_buffers;
326         std::unordered_map<int, PlayingSound*> m_sounds_playing;
327         struct FadeState {
328                 FadeState() = default;
329
330                 FadeState(float step, float current_gain, float target_gain):
331                         step(step),
332                         current_gain(current_gain),
333                         target_gain(target_gain) {}
334                 float step;
335                 float current_gain;
336                 float target_gain;
337         };
338
339         std::unordered_map<int, FadeState> m_sounds_fading;
340 public:
341         OpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher):
342                 m_fetcher(fetcher),
343                 m_device(smg->m_device.get()),
344                 m_context(smg->m_context.get()),
345                 m_next_id(1)
346         {
347                 infostream << "Audio: Initialized: OpenAL " << std::endl;
348         }
349
350         ~OpenALSoundManager()
351         {
352                 infostream << "Audio: Deinitializing..." << std::endl;
353
354                 std::unordered_set<int> source_del_list;
355
356                 for (const auto &sp : m_sounds_playing)
357                         source_del_list.insert(sp.first);
358
359                 for (const auto &id : source_del_list)
360                         deleteSound(id);
361
362                 for (auto &buffer : m_buffers) {
363                         for (SoundBuffer *sb : buffer.second) {
364                                 delete sb;
365                         }
366                         buffer.second.clear();
367                 }
368                 m_buffers.clear();
369
370                 infostream << "Audio: Deinitialized." << std::endl;
371         }
372
373         void step(float dtime)
374         {
375                 doFades(dtime);
376         }
377
378         void addBuffer(const std::string &name, SoundBuffer *buf)
379         {
380                 std::unordered_map<std::string, std::vector<SoundBuffer*>>::iterator i =
381                                 m_buffers.find(name);
382                 if(i != m_buffers.end()){
383                         i->second.push_back(buf);
384                         return;
385                 }
386                 std::vector<SoundBuffer*> bufs;
387                 bufs.push_back(buf);
388                 m_buffers[name] = bufs;
389         }
390
391         SoundBuffer* getBuffer(const std::string &name)
392         {
393                 std::unordered_map<std::string, std::vector<SoundBuffer*>>::iterator i =
394                                 m_buffers.find(name);
395                 if(i == m_buffers.end())
396                         return nullptr;
397                 std::vector<SoundBuffer*> &bufs = i->second;
398                 int j = myrand() % bufs.size();
399                 return bufs[j];
400         }
401
402         PlayingSound* createPlayingSound(SoundBuffer *buf, bool loop,
403                         float volume, float pitch)
404         {
405                 infostream << "OpenALSoundManager: Creating playing sound" << std::endl;
406                 assert(buf);
407                 PlayingSound *sound = new PlayingSound;
408                 assert(sound);
409                 warn_if_error(alGetError(), "before createPlayingSound");
410                 alGenSources(1, &sound->source_id);
411                 alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
412                 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, true);
413                 alSource3f(sound->source_id, AL_POSITION, 0, 0, 0);
414                 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
415                 alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
416                 volume = std::fmax(0.0f, volume);
417                 alSourcef(sound->source_id, AL_GAIN, volume);
418                 alSourcef(sound->source_id, AL_PITCH, pitch);
419                 alSourcePlay(sound->source_id);
420                 warn_if_error(alGetError(), "createPlayingSound");
421                 return sound;
422         }
423
424         PlayingSound* createPlayingSoundAt(SoundBuffer *buf, bool loop,
425                         float volume, v3f pos, float pitch)
426         {
427                 infostream << "OpenALSoundManager: Creating positional playing sound"
428                                 << std::endl;
429                 assert(buf);
430                 PlayingSound *sound = new PlayingSound;
431                 assert(sound);
432                 warn_if_error(alGetError(), "before createPlayingSoundAt");
433                 alGenSources(1, &sound->source_id);
434                 alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
435                 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
436                 alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
437                 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
438                 // Use alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED) and set reference
439                 // distance to clamp gain at <1 node distance, to avoid excessive
440                 // volume when closer
441                 alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 10.0f);
442                 alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
443                 // Multiply by 3 to compensate for reducing AL_REFERENCE_DISTANCE from
444                 // the previous value of 30 to the new value of 10
445                 volume = std::fmax(0.0f, volume * 3.0f);
446                 alSourcef(sound->source_id, AL_GAIN, volume);
447                 alSourcef(sound->source_id, AL_PITCH, pitch);
448                 alSourcePlay(sound->source_id);
449                 warn_if_error(alGetError(), "createPlayingSoundAt");
450                 return sound;
451         }
452
453         int playSoundRaw(SoundBuffer *buf, bool loop, float volume, float pitch)
454         {
455                 assert(buf);
456                 PlayingSound *sound = createPlayingSound(buf, loop, volume, pitch);
457                 if(!sound)
458                         return -1;
459                 int id = m_next_id++;
460                 m_sounds_playing[id] = sound;
461                 return id;
462         }
463
464         int playSoundRawAt(SoundBuffer *buf, bool loop, float volume, const v3f &pos,
465                         float pitch)
466         {
467                 assert(buf);
468                 PlayingSound *sound = createPlayingSoundAt(buf, loop, volume, pos, pitch);
469                 if(!sound)
470                         return -1;
471                 int id = m_next_id++;
472                 m_sounds_playing[id] = sound;
473                 return id;
474         }
475
476         void deleteSound(int id)
477         {
478                 std::unordered_map<int, PlayingSound*>::iterator i = m_sounds_playing.find(id);
479                 if(i == m_sounds_playing.end())
480                         return;
481                 PlayingSound *sound = i->second;
482
483                 alDeleteSources(1, &sound->source_id);
484
485                 delete sound;
486                 m_sounds_playing.erase(id);
487         }
488
489         /* If buffer does not exist, consult the fetcher */
490         SoundBuffer* getFetchBuffer(const std::string &name)
491         {
492                 SoundBuffer *buf = getBuffer(name);
493                 if(buf)
494                         return buf;
495                 if(!m_fetcher)
496                         return nullptr;
497                 std::set<std::string> paths;
498                 std::set<std::string> datas;
499                 m_fetcher->fetchSounds(name, paths, datas);
500                 for (const std::string &path : paths) {
501                         loadSoundFile(name, path);
502                 }
503                 for (const std::string &data : datas) {
504                         loadSoundData(name, data);
505                 }
506                 return getBuffer(name);
507         }
508
509         // Remove stopped sounds
510         void maintain()
511         {
512                 if (!m_sounds_playing.empty()) {
513                         verbosestream << "OpenALSoundManager::maintain(): "
514                                         << m_sounds_playing.size() <<" playing sounds, "
515                                         << m_buffers.size() <<" sound names loaded"<<std::endl;
516                 }
517                 std::unordered_set<int> del_list;
518                 for (const auto &sp : m_sounds_playing) {
519                         int id = sp.first;
520                         PlayingSound *sound = sp.second;
521                         // If not playing, remove it
522                         {
523                                 ALint state;
524                                 alGetSourcei(sound->source_id, AL_SOURCE_STATE, &state);
525                                 if(state != AL_PLAYING){
526                                         del_list.insert(id);
527                                 }
528                         }
529                 }
530                 if(!del_list.empty())
531                         verbosestream<<"OpenALSoundManager::maintain(): deleting "
532                                         <<del_list.size()<<" playing sounds"<<std::endl;
533                 for (int i : del_list) {
534                         deleteSound(i);
535                 }
536         }
537
538         /* Interface */
539
540         bool loadSoundFile(const std::string &name,
541                         const std::string &filepath)
542         {
543                 SoundBuffer *buf = load_ogg_from_file(filepath);
544                 if (buf)
545                         addBuffer(name, buf);
546                 return !!buf;
547         }
548
549         bool loadSoundData(const std::string &name,
550                         const std::string &filedata)
551         {
552                 SoundBuffer *buf = load_ogg_from_buffer(filedata, name);
553                 if (buf)
554                         addBuffer(name, buf);
555                 return !!buf;
556         }
557
558         void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up)
559         {
560                 alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
561                 alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
562                 ALfloat f[6];
563                 f3_set(f, at);
564                 f3_set(f+3, -up);
565                 alListenerfv(AL_ORIENTATION, f);
566                 warn_if_error(alGetError(), "updateListener");
567         }
568
569         void setListenerGain(float gain)
570         {
571                 alListenerf(AL_GAIN, gain);
572         }
573
574         int playSound(const std::string &name, bool loop, float volume, float fade, float pitch)
575         {
576                 maintain();
577                 if (name.empty())
578                         return 0;
579                 SoundBuffer *buf = getFetchBuffer(name);
580                 if(!buf){
581                         infostream << "OpenALSoundManager: \"" << name << "\" not found."
582                                         << std::endl;
583                         return -1;
584                 }
585                 int handle = -1;
586                 if (fade > 0) {
587                         handle = playSoundRaw(buf, loop, 0.0f, pitch);
588                         fadeSound(handle, fade, volume);
589                 } else {
590                         handle = playSoundRaw(buf, loop, volume, pitch);
591                 }
592                 return handle;
593         }
594
595         int playSoundAt(const std::string &name, bool loop, float volume, v3f pos, float pitch)
596         {
597                 maintain();
598                 if (name.empty())
599                         return 0;
600                 SoundBuffer *buf = getFetchBuffer(name);
601                 if(!buf){
602                         infostream << "OpenALSoundManager: \"" << name << "\" not found."
603                                         << std::endl;
604                         return -1;
605                 }
606                 return playSoundRawAt(buf, loop, volume, pos, pitch);
607         }
608
609         void stopSound(int sound)
610         {
611                 maintain();
612                 deleteSound(sound);
613         }
614
615         void fadeSound(int soundid, float step, float gain)
616         {
617                 // Ignore the command if step isn't valid.
618                 if (step == 0)
619                         return;
620                 float current_gain = getSoundGain(soundid);
621                 step = gain - current_gain > 0 ? abs(step) : -abs(step);
622                 if (m_sounds_fading.find(soundid) != m_sounds_fading.end()) {
623                         auto current_fade = m_sounds_fading[soundid];
624                         // Do not replace the fade if it's equivalent.
625                         if (current_fade.target_gain == gain && current_fade.step == step)
626                                 return;
627                         m_sounds_fading.erase(soundid);
628                 }
629                 gain = rangelim(gain, 0, 1);
630                 m_sounds_fading[soundid] = FadeState(step, current_gain, gain);
631         }
632
633         void doFades(float dtime)
634         {
635                 for (auto i = m_sounds_fading.begin(); i != m_sounds_fading.end();) {
636                         FadeState& fade = i->second;
637                         assert(fade.step != 0);
638                         fade.current_gain += (fade.step * dtime);
639
640                         if (fade.step < 0.f)
641                                 fade.current_gain = std::max(fade.current_gain, fade.target_gain);
642                         else
643                                 fade.current_gain = std::min(fade.current_gain, fade.target_gain);
644
645                         if (fade.current_gain <= 0.f)
646                                 stopSound(i->first);
647                         else
648                                 updateSoundGain(i->first, fade.current_gain);
649
650                         // The increment must happen during the erase call, or else it'll segfault.
651                         if (fade.current_gain == fade.target_gain)
652                                 m_sounds_fading.erase(i++);
653                         else
654                                 i++;
655                 }
656         }
657
658         bool soundExists(int sound)
659         {
660                 maintain();
661                 return (m_sounds_playing.count(sound) != 0);
662         }
663
664         void updateSoundPosition(int id, v3f pos)
665         {
666                 auto i = m_sounds_playing.find(id);
667                 if (i == m_sounds_playing.end())
668                         return;
669                 PlayingSound *sound = i->second;
670
671                 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
672                 alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
673                 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
674                 alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0);
675         }
676
677         bool updateSoundGain(int id, float gain)
678         {
679                 auto i = m_sounds_playing.find(id);
680                 if (i == m_sounds_playing.end())
681                         return false;
682
683                 PlayingSound *sound = i->second;
684                 alSourcef(sound->source_id, AL_GAIN, gain);
685                 return true;
686         }
687
688         float getSoundGain(int id)
689         {
690                 auto i = m_sounds_playing.find(id);
691                 if (i == m_sounds_playing.end())
692                         return 0;
693
694                 PlayingSound *sound = i->second;
695                 ALfloat gain;
696                 alGetSourcef(sound->source_id, AL_GAIN, &gain);
697                 return gain;
698         }
699 };
700
701 std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
702 {
703         auto smg = std::make_shared<SoundManagerSingleton>();
704         if (!smg->init()) {
705                 smg.reset();
706         }
707         return smg;
708 }
709
710 ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher)
711 {
712         return new OpenALSoundManager(smg, fetcher);
713 };