]> git.lizzy.rs Git - dragonfireclient.git/blob - src/sound_openal.cpp
Initially split utility.h to multiple files in util/
[dragonfireclient.git] / src / sound_openal.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2012 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 <vorbis/vorbisfile.h>
40 #include "log.h"
41 #include "utility.h" // myrand()
42 #include "filesys.h"
43 #include <map>
44 #include <vector>
45 #include <fstream>
46
47 #define BUFFER_SIZE 30000
48
49 static const char *alcErrorString(ALCenum err)
50 {
51         switch (err) {
52         case ALC_NO_ERROR:
53                 return "no error";
54         case ALC_INVALID_DEVICE:
55                 return "invalid device";
56         case ALC_INVALID_CONTEXT:
57                 return "invalid context";
58         case ALC_INVALID_ENUM:
59                 return "invalid enum";
60         case ALC_INVALID_VALUE:
61                 return "invalid value";
62         case ALC_OUT_OF_MEMORY:
63                 return "out of memory";
64         default:
65                 return "<unknown OpenAL error>";
66         }
67 }
68
69 static const char *alErrorString(ALenum err)
70 {
71         switch (err) {
72         case AL_NO_ERROR:
73                 return "no error";
74         case AL_INVALID_NAME:
75                 return "invalid name";
76         case AL_INVALID_ENUM:
77                 return "invalid enum";
78         case AL_INVALID_VALUE:
79                 return "invalid value";
80         case AL_INVALID_OPERATION:
81                 return "invalid operation";
82         case AL_OUT_OF_MEMORY:
83                 return "out of memory";
84         default:
85                 return "<unknown OpenAL error>";
86         }
87 }
88
89 static ALenum warn_if_error(ALenum err, const char *desc)
90 {
91         if(err == AL_NO_ERROR)
92                 return err;
93         errorstream<<"WARNING: "<<desc<<": "<<alErrorString(err)<<std::endl;
94         return err;
95 }
96
97 void f3_set(ALfloat *f3, v3f v)
98 {
99         f3[0] = v.X;
100         f3[1] = v.Y;
101         f3[2] = v.Z;
102 }
103
104 struct SoundBuffer
105 {
106         ALenum format;
107         ALsizei freq;
108         ALuint buffer_id;
109         std::vector<char> buffer;
110 };
111
112 SoundBuffer* loadOggFile(const std::string &filepath)
113 {
114         int endian = 0; // 0 for Little-Endian, 1 for Big-Endian
115         int bitStream;
116         long bytes;
117         char array[BUFFER_SIZE]; // Local fixed size array
118         vorbis_info *pInfo;
119         OggVorbis_File oggFile;
120         
121         // Do a dumb-ass static string copy for old versions of ov_fopen
122         // because they expect a non-const char*
123         char nonconst[10000];
124         snprintf(nonconst, 10000, "%s", filepath.c_str());
125         // Try opening the given file
126         //if(ov_fopen(filepath.c_str(), &oggFile) != 0)
127         if(ov_fopen(nonconst, &oggFile) != 0)
128         {
129                 infostream<<"Audio: Error opening "<<filepath<<" for decoding"<<std::endl;
130                 return NULL;
131         }
132
133         SoundBuffer *snd = new SoundBuffer;
134
135         // Get some information about the OGG file
136         pInfo = ov_info(&oggFile, -1);
137
138         // Check the number of channels... always use 16-bit samples
139         if(pInfo->channels == 1)
140                 snd->format = AL_FORMAT_MONO16;
141         else
142                 snd->format = AL_FORMAT_STEREO16;
143
144         // The frequency of the sampling rate
145         snd->freq = pInfo->rate;
146
147         // Keep reading until all is read
148         do
149         {
150                 // Read up to a buffer's worth of decoded sound data
151                 bytes = ov_read(&oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream);
152
153                 if(bytes < 0)
154                 {
155                         ov_clear(&oggFile);
156                         infostream<<"Audio: Error decoding "<<filepath<<std::endl;
157                         return NULL;
158                 }
159
160                 // Append to end of buffer
161                 snd->buffer.insert(snd->buffer.end(), array, array + bytes);
162         } while (bytes > 0);
163
164         alGenBuffers(1, &snd->buffer_id);
165         alBufferData(snd->buffer_id, snd->format,
166                         &(snd->buffer[0]), snd->buffer.size(),
167                         snd->freq);
168
169         ALenum error = alGetError();
170
171         if(error != AL_NO_ERROR){
172                 infostream<<"Audio: OpenAL error: "<<alErrorString(error)
173                                 <<"preparing sound buffer"<<std::endl;
174         }
175
176         infostream<<"Audio file "<<filepath<<" loaded"<<std::endl;
177
178         // Clean up!
179         ov_clear(&oggFile);
180
181         return snd;
182 }
183
184 struct PlayingSound
185 {
186         ALuint source_id;
187         bool loop;
188 };
189
190 class OpenALSoundManager: public ISoundManager
191 {
192 private:
193         OnDemandSoundFetcher *m_fetcher;
194         ALCdevice *m_device;
195         ALCcontext *m_context;
196         bool m_can_vorbis;
197         int m_next_id;
198         std::map<std::string, std::vector<SoundBuffer*> > m_buffers;
199         std::map<int, PlayingSound*> m_sounds_playing;
200         v3f m_listener_pos;
201 public:
202         bool m_is_initialized;
203         OpenALSoundManager(OnDemandSoundFetcher *fetcher):
204                 m_fetcher(fetcher),
205                 m_device(NULL),
206                 m_context(NULL),
207                 m_can_vorbis(false),
208                 m_next_id(1),
209                 m_is_initialized(false)
210         {
211                 ALCenum error = ALC_NO_ERROR;
212                 
213                 infostream<<"Audio: Initializing..."<<std::endl;
214
215                 m_device = alcOpenDevice(NULL);
216                 if(!m_device){
217                         infostream<<"Audio: No audio device available, audio system "
218                                 <<"not initialized"<<std::endl;
219                         return;
220                 }
221
222                 if(alcIsExtensionPresent(m_device, "EXT_vorbis")){
223                         infostream<<"Audio: Vorbis extension present"<<std::endl;
224                         m_can_vorbis = true;
225                 } else{
226                         infostream<<"Audio: Vorbis extension NOT present"<<std::endl;
227                         m_can_vorbis = false;
228                 }
229
230                 m_context = alcCreateContext(m_device, NULL);
231                 if(!m_context){
232                         error = alcGetError(m_device);
233                         infostream<<"Audio: Unable to initialize audio context, "
234                                         <<"aborting audio initialization ("<<alcErrorString(error)
235                                         <<")"<<std::endl;
236                         alcCloseDevice(m_device);
237                         m_device = NULL;
238                         return;
239                 }
240
241                 if(!alcMakeContextCurrent(m_context) ||
242                                 (error = alcGetError(m_device) != ALC_NO_ERROR))
243                 {
244                         infostream<<"Audio: Error setting audio context, aborting audio "
245                                         <<"initialization ("<<alcErrorString(error)<<")"<<std::endl;
246                         alcDestroyContext(m_context);
247                         m_context = NULL;
248                         alcCloseDevice(m_device);
249                         m_device = NULL;
250                         return;
251                 }
252
253                 alDistanceModel(AL_EXPONENT_DISTANCE);
254
255                 infostream<<"Audio: Initialized: OpenAL "<<alGetString(AL_VERSION)
256                                 <<", using "<<alcGetString(m_device, ALC_DEVICE_SPECIFIER)
257                                 <<std::endl;
258
259                 m_is_initialized = true;
260         }
261
262         ~OpenALSoundManager()
263         {
264                 infostream<<"Audio: Deinitializing..."<<std::endl;
265                 // KABOOM!
266                 // TODO: Clear SoundBuffers
267                 alcMakeContextCurrent(NULL);
268                 alcDestroyContext(m_context);
269                 m_context = NULL;
270                 alcCloseDevice(m_device);
271                 m_device = NULL;
272                 infostream<<"Audio: Deinitialized."<<std::endl;
273         }
274         
275         void addBuffer(const std::string &name, SoundBuffer *buf)
276         {
277                 std::map<std::string, std::vector<SoundBuffer*> >::iterator i =
278                                 m_buffers.find(name);
279                 if(i != m_buffers.end()){
280                         i->second.push_back(buf);
281                         return;
282                 }
283                 std::vector<SoundBuffer*> bufs;
284                 bufs.push_back(buf);
285                 m_buffers[name] = bufs;
286                 return;
287         }
288
289         SoundBuffer* getBuffer(const std::string &name)
290         {
291                 std::map<std::string, std::vector<SoundBuffer*> >::iterator i =
292                                 m_buffers.find(name);
293                 if(i == m_buffers.end())
294                         return NULL;
295                 std::vector<SoundBuffer*> &bufs = i->second;
296                 int j = myrand() % bufs.size();
297                 return bufs[j];
298         }
299
300         PlayingSound* createPlayingSound(SoundBuffer *buf, bool loop,
301                         float volume)
302         {
303                 infostream<<"OpenALSoundManager: Creating playing sound"<<std::endl;
304                 assert(buf);
305                 PlayingSound *sound = new PlayingSound;
306                 assert(sound);
307                 warn_if_error(alGetError(), "before createPlayingSound");
308                 alGenSources(1, &sound->source_id);
309                 alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
310                 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, true);
311                 alSource3f(sound->source_id, AL_POSITION, 0, 0, 0);
312                 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
313                 alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
314                 volume = MYMAX(0.0, volume);
315                 alSourcef(sound->source_id, AL_GAIN, volume);
316                 alSourcePlay(sound->source_id);
317                 warn_if_error(alGetError(), "createPlayingSound");
318                 return sound;
319         }
320
321         PlayingSound* createPlayingSoundAt(SoundBuffer *buf, bool loop,
322                         float volume, v3f pos)
323         {
324                 infostream<<"OpenALSoundManager: Creating positional playing sound"
325                                 <<std::endl;
326                 assert(buf);
327                 PlayingSound *sound = new PlayingSound;
328                 assert(sound);
329                 warn_if_error(alGetError(), "before createPlayingSoundAt");
330                 alGenSources(1, &sound->source_id);
331                 alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
332                 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
333                 alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
334                 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
335                 //alSourcef(sound->source_id, AL_ROLLOFF_FACTOR, 0.7);
336                 alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0);
337                 alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
338                 volume = MYMAX(0.0, volume);
339                 alSourcef(sound->source_id, AL_GAIN, volume);
340                 alSourcePlay(sound->source_id);
341                 warn_if_error(alGetError(), "createPlayingSoundAt");
342                 return sound;
343         }
344
345         int playSoundRaw(SoundBuffer *buf, bool loop, float volume)
346         {
347                 assert(buf);
348                 PlayingSound *sound = createPlayingSound(buf, loop, volume);
349                 if(!sound)
350                         return -1;
351                 int id = m_next_id++;
352                 m_sounds_playing[id] = sound;
353                 return id;
354         }
355
356         int playSoundRawAt(SoundBuffer *buf, bool loop, float volume, v3f pos)
357         {
358                 assert(buf);
359                 PlayingSound *sound = createPlayingSoundAt(buf, loop, volume, pos);
360                 if(!sound)
361                         return -1;
362                 int id = m_next_id++;
363                 m_sounds_playing[id] = sound;
364                 return id;
365         }
366         
367         void deleteSound(int id)
368         {
369                 std::map<int, PlayingSound*>::iterator i =
370                                 m_sounds_playing.find(id);
371                 if(i == m_sounds_playing.end())
372                         return;
373                 PlayingSound *sound = i->second;
374                 
375                 alDeleteSources(1, &sound->source_id);
376
377                 delete sound;
378                 m_sounds_playing.erase(id);
379         }
380
381         /* If buffer does not exist, consult the fetcher */
382         SoundBuffer* getFetchBuffer(const std::string name)
383         {
384                 SoundBuffer *buf = getBuffer(name);
385                 if(buf)
386                         return buf;
387                 if(!m_fetcher)
388                         return NULL;
389                 std::set<std::string> paths;
390                 std::set<std::string> datas;
391                 m_fetcher->fetchSounds(name, paths, datas);
392                 for(std::set<std::string>::iterator i = paths.begin();
393                                 i != paths.end(); i++){
394                         loadSoundFile(name, *i);
395                 }
396                 for(std::set<std::string>::iterator i = datas.begin();
397                                 i != datas.end(); i++){
398                         loadSoundData(name, *i);
399                 }
400                 return getBuffer(name);
401         }
402         
403         // Remove stopped sounds
404         void maintain()
405         {
406                 verbosestream<<"OpenALSoundManager::maintain(): "
407                                 <<m_sounds_playing.size()<<" playing sounds, "
408                                 <<m_buffers.size()<<" sound names loaded"<<std::endl;
409                 std::set<int> del_list;
410                 for(std::map<int, PlayingSound*>::iterator
411                                 i = m_sounds_playing.begin();
412                                 i != m_sounds_playing.end(); i++)
413                 {
414                         int id = i->first;
415                         PlayingSound *sound = i->second;
416                         // If not playing, remove it
417                         {
418                                 ALint state;
419                                 alGetSourcei(sound->source_id, AL_SOURCE_STATE, &state);
420                                 if(state != AL_PLAYING){
421                                         del_list.insert(id);
422                                 }
423                         }
424                 }
425                 if(del_list.size() != 0)
426                         verbosestream<<"OpenALSoundManager::maintain(): deleting "
427                                         <<del_list.size()<<" playing sounds"<<std::endl;
428                 for(std::set<int>::iterator i = del_list.begin();
429                                 i != del_list.end(); i++)
430                 {
431                         deleteSound(*i);
432                 }
433         }
434
435         /* Interface */
436
437         bool loadSoundFile(const std::string &name,
438                         const std::string &filepath)
439         {
440                 SoundBuffer *buf = loadOggFile(filepath);
441                 if(buf)
442                         addBuffer(name, buf);
443                 return false;
444         }
445         bool loadSoundData(const std::string &name,
446                         const std::string &filedata)
447         {
448                 // The vorbis API sucks; just write it to a file and use vorbisfile
449                 // TODO: Actually load it directly from memory
450                 std::string basepath = porting::path_user + DIR_DELIM + "cache" +
451                                 DIR_DELIM + "tmp";
452                 std::string path = basepath + DIR_DELIM + "tmp.ogg";
453                 verbosestream<<"OpenALSoundManager::loadSoundData(): Writing "
454                                 <<"temporary file to ["<<path<<"]"<<std::endl;
455                 fs::CreateAllDirs(basepath);
456                 std::ofstream of(path.c_str(), std::ios::binary);
457                 of.write(filedata.c_str(), filedata.size());
458                 of.close();
459                 return loadSoundFile(name, path);
460         }
461
462         void updateListener(v3f pos, v3f vel, v3f at, v3f up)
463         {
464                 m_listener_pos = pos;
465                 alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
466                 alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
467                 ALfloat f[6];
468                 f3_set(f, at);
469                 f3_set(f+3, -up);
470                 alListenerfv(AL_ORIENTATION, f);
471                 warn_if_error(alGetError(), "updateListener");
472         }
473         
474         void setListenerGain(float gain)
475         {
476                 alListenerf(AL_GAIN, gain);
477         }
478
479         int playSound(const std::string &name, bool loop, float volume)
480         {
481                 maintain();
482                 if(name == "")
483                         return 0;
484                 SoundBuffer *buf = getFetchBuffer(name);
485                 if(!buf){
486                         infostream<<"OpenALSoundManager: \""<<name<<"\" not found."
487                                         <<std::endl;
488                         return -1;
489                 }
490                 return playSoundRaw(buf, loop, volume);
491         }
492         int playSoundAt(const std::string &name, bool loop, float volume, v3f pos)
493         {
494                 maintain();
495                 if(name == "")
496                         return 0;
497                 SoundBuffer *buf = getFetchBuffer(name);
498                 if(!buf){
499                         infostream<<"OpenALSoundManager: \""<<name<<"\" not found."
500                                         <<std::endl;
501                         return -1;
502                 }
503                 return playSoundRawAt(buf, loop, volume, pos);
504         }
505         void stopSound(int sound)
506         {
507                 maintain();
508                 deleteSound(sound);
509         }
510         bool soundExists(int sound)
511         {
512                 maintain();
513                 return (m_sounds_playing.count(sound) != 0);
514         }
515         void updateSoundPosition(int id, v3f pos)
516         {
517                 std::map<int, PlayingSound*>::iterator i =
518                                 m_sounds_playing.find(id);
519                 if(i == m_sounds_playing.end())
520                         return;
521                 PlayingSound *sound = i->second;
522
523                 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
524                 alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
525                 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
526                 alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0);
527         }
528 };
529
530 ISoundManager *createOpenALSoundManager(OnDemandSoundFetcher *fetcher)
531 {
532         OpenALSoundManager *m = new OpenALSoundManager(fetcher);
533         if(m->m_is_initialized)
534                 return m;
535         delete m;
536         return NULL;
537 };
538