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