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