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>
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.
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.
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.
24 #include "sound_openal.h"
30 #elif defined(__APPLE__)
31 #include <OpenAL/al.h>
32 #include <OpenAL/alc.h>
33 //#include <OpenAL/alext.h>
39 #include <vorbis/vorbisfile.h>
43 #include "util/numeric.h" // myrand()
49 #define BUFFER_SIZE 30000
51 static const char *alcErrorString(ALCenum err)
56 case ALC_INVALID_DEVICE:
57 return "invalid device";
58 case ALC_INVALID_CONTEXT:
59 return "invalid context";
60 case ALC_INVALID_ENUM:
61 return "invalid enum";
62 case ALC_INVALID_VALUE:
63 return "invalid value";
64 case ALC_OUT_OF_MEMORY:
65 return "out of memory";
67 return "<unknown OpenAL error>";
71 static const char *alErrorString(ALenum err)
77 return "invalid name";
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";
87 return "<unknown OpenAL error>";
91 static ALenum warn_if_error(ALenum err, const char *desc)
93 if(err == AL_NO_ERROR)
95 errorstream<<"WARNING: "<<desc<<": "<<alErrorString(err)<<std::endl;
99 void f3_set(ALfloat *f3, v3f v)
111 std::vector<char> buffer;
114 SoundBuffer* loadOggFile(const std::string &filepath)
116 int endian = 0; // 0 for Little-Endian, 1 for Big-Endian
119 char array[BUFFER_SIZE]; // Local fixed size array
121 OggVorbis_File oggFile;
123 // Do a dumb-ass static string copy for old versions of ov_fopen
124 // because they expect a non-const char*
125 char nonconst[10000];
126 snprintf(nonconst, 10000, "%s", filepath.c_str());
127 // Try opening the given file
128 //if(ov_fopen(filepath.c_str(), &oggFile) != 0)
129 if(ov_fopen(nonconst, &oggFile) != 0)
131 infostream<<"Audio: Error opening "<<filepath<<" for decoding"<<std::endl;
135 SoundBuffer *snd = new SoundBuffer;
137 // Get some information about the OGG file
138 pInfo = ov_info(&oggFile, -1);
140 // Check the number of channels... always use 16-bit samples
141 if(pInfo->channels == 1)
142 snd->format = AL_FORMAT_MONO16;
144 snd->format = AL_FORMAT_STEREO16;
146 // The frequency of the sampling rate
147 snd->freq = pInfo->rate;
149 // Keep reading until all is read
152 // Read up to a buffer's worth of decoded sound data
153 bytes = ov_read(&oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream);
158 infostream<<"Audio: Error decoding "<<filepath<<std::endl;
162 // Append to end of buffer
163 snd->buffer.insert(snd->buffer.end(), array, array + bytes);
166 alGenBuffers(1, &snd->buffer_id);
167 alBufferData(snd->buffer_id, snd->format,
168 &(snd->buffer[0]), snd->buffer.size(),
171 ALenum error = alGetError();
173 if(error != AL_NO_ERROR){
174 infostream<<"Audio: OpenAL error: "<<alErrorString(error)
175 <<"preparing sound buffer"<<std::endl;
178 infostream<<"Audio file "<<filepath<<" loaded"<<std::endl;
192 class OpenALSoundManager: public ISoundManager
195 OnDemandSoundFetcher *m_fetcher;
197 ALCcontext *m_context;
200 std::map<std::string, std::vector<SoundBuffer*> > m_buffers;
201 std::map<int, PlayingSound*> m_sounds_playing;
204 bool m_is_initialized;
205 OpenALSoundManager(OnDemandSoundFetcher *fetcher):
211 m_is_initialized(false)
213 ALCenum error = ALC_NO_ERROR;
215 infostream<<"Audio: Initializing..."<<std::endl;
217 m_device = alcOpenDevice(NULL);
219 infostream<<"Audio: No audio device available, audio system "
220 <<"not initialized"<<std::endl;
224 if(alcIsExtensionPresent(m_device, "EXT_vorbis")){
225 infostream<<"Audio: Vorbis extension present"<<std::endl;
228 infostream<<"Audio: Vorbis extension NOT present"<<std::endl;
229 m_can_vorbis = false;
232 m_context = alcCreateContext(m_device, NULL);
234 error = alcGetError(m_device);
235 infostream<<"Audio: Unable to initialize audio context, "
236 <<"aborting audio initialization ("<<alcErrorString(error)
238 alcCloseDevice(m_device);
243 if(!alcMakeContextCurrent(m_context) ||
244 (error = alcGetError(m_device) != ALC_NO_ERROR))
246 infostream<<"Audio: Error setting audio context, aborting audio "
247 <<"initialization ("<<alcErrorString(error)<<")"<<std::endl;
248 alcDestroyContext(m_context);
250 alcCloseDevice(m_device);
255 alDistanceModel(AL_EXPONENT_DISTANCE);
257 infostream<<"Audio: Initialized: OpenAL "<<alGetString(AL_VERSION)
258 <<", using "<<alcGetString(m_device, ALC_DEVICE_SPECIFIER)
261 m_is_initialized = true;
264 ~OpenALSoundManager()
266 infostream<<"Audio: Deinitializing..."<<std::endl;
268 // TODO: Clear SoundBuffers
269 alcMakeContextCurrent(NULL);
270 alcDestroyContext(m_context);
272 alcCloseDevice(m_device);
275 for (std::map<std::string, std::vector<SoundBuffer*> >::iterator i = m_buffers.begin();
276 i != m_buffers.end(); i++) {
277 for (std::vector<SoundBuffer*>::iterator iter = (*i).second.begin();
278 iter != (*i).second.end(); iter++) {
284 infostream<<"Audio: Deinitialized."<<std::endl;
287 void addBuffer(const std::string &name, SoundBuffer *buf)
289 std::map<std::string, std::vector<SoundBuffer*> >::iterator i =
290 m_buffers.find(name);
291 if(i != m_buffers.end()){
292 i->second.push_back(buf);
295 std::vector<SoundBuffer*> bufs;
297 m_buffers[name] = bufs;
301 SoundBuffer* getBuffer(const std::string &name)
303 std::map<std::string, std::vector<SoundBuffer*> >::iterator i =
304 m_buffers.find(name);
305 if(i == m_buffers.end())
307 std::vector<SoundBuffer*> &bufs = i->second;
308 int j = myrand() % bufs.size();
312 PlayingSound* createPlayingSound(SoundBuffer *buf, bool loop,
315 infostream<<"OpenALSoundManager: Creating playing sound"<<std::endl;
317 PlayingSound *sound = new PlayingSound;
319 warn_if_error(alGetError(), "before createPlayingSound");
320 alGenSources(1, &sound->source_id);
321 alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
322 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, true);
323 alSource3f(sound->source_id, AL_POSITION, 0, 0, 0);
324 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
325 alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
326 volume = MYMAX(0.0, volume);
327 alSourcef(sound->source_id, AL_GAIN, volume);
328 alSourcePlay(sound->source_id);
329 warn_if_error(alGetError(), "createPlayingSound");
333 PlayingSound* createPlayingSoundAt(SoundBuffer *buf, bool loop,
334 float volume, v3f pos)
336 infostream<<"OpenALSoundManager: Creating positional playing sound"
339 PlayingSound *sound = new PlayingSound;
341 warn_if_error(alGetError(), "before createPlayingSoundAt");
342 alGenSources(1, &sound->source_id);
343 alSourcei(sound->source_id, AL_BUFFER, buf->buffer_id);
344 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
345 alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
346 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
347 //alSourcef(sound->source_id, AL_ROLLOFF_FACTOR, 0.7);
348 alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0);
349 alSourcei(sound->source_id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
350 volume = MYMAX(0.0, volume);
351 alSourcef(sound->source_id, AL_GAIN, volume);
352 alSourcePlay(sound->source_id);
353 warn_if_error(alGetError(), "createPlayingSoundAt");
357 int playSoundRaw(SoundBuffer *buf, bool loop, float volume)
360 PlayingSound *sound = createPlayingSound(buf, loop, volume);
363 int id = m_next_id++;
364 m_sounds_playing[id] = sound;
368 int playSoundRawAt(SoundBuffer *buf, bool loop, float volume, v3f pos)
371 PlayingSound *sound = createPlayingSoundAt(buf, loop, volume, pos);
374 int id = m_next_id++;
375 m_sounds_playing[id] = sound;
379 void deleteSound(int id)
381 std::map<int, PlayingSound*>::iterator i =
382 m_sounds_playing.find(id);
383 if(i == m_sounds_playing.end())
385 PlayingSound *sound = i->second;
387 alDeleteSources(1, &sound->source_id);
390 m_sounds_playing.erase(id);
393 /* If buffer does not exist, consult the fetcher */
394 SoundBuffer* getFetchBuffer(const std::string &name)
396 SoundBuffer *buf = getBuffer(name);
401 std::set<std::string> paths;
402 std::set<std::string> datas;
403 m_fetcher->fetchSounds(name, paths, datas);
404 for(std::set<std::string>::iterator i = paths.begin();
405 i != paths.end(); i++){
406 loadSoundFile(name, *i);
408 for(std::set<std::string>::iterator i = datas.begin();
409 i != datas.end(); i++){
410 loadSoundData(name, *i);
412 return getBuffer(name);
415 // Remove stopped sounds
418 verbosestream<<"OpenALSoundManager::maintain(): "
419 <<m_sounds_playing.size()<<" playing sounds, "
420 <<m_buffers.size()<<" sound names loaded"<<std::endl;
421 std::set<int> del_list;
422 for(std::map<int, PlayingSound*>::iterator
423 i = m_sounds_playing.begin();
424 i != m_sounds_playing.end(); i++)
427 PlayingSound *sound = i->second;
428 // If not playing, remove it
431 alGetSourcei(sound->source_id, AL_SOURCE_STATE, &state);
432 if(state != AL_PLAYING){
437 if(!del_list.empty())
438 verbosestream<<"OpenALSoundManager::maintain(): deleting "
439 <<del_list.size()<<" playing sounds"<<std::endl;
440 for(std::set<int>::iterator i = del_list.begin();
441 i != del_list.end(); i++)
449 bool loadSoundFile(const std::string &name,
450 const std::string &filepath)
452 SoundBuffer *buf = loadOggFile(filepath);
454 addBuffer(name, buf);
457 bool loadSoundData(const std::string &name,
458 const std::string &filedata)
460 // The vorbis API sucks; just write it to a file and use vorbisfile
461 // TODO: Actually load it directly from memory
462 std::string basepath = porting::path_user + DIR_DELIM + "cache" +
464 std::string path = basepath + DIR_DELIM + "tmp.ogg";
465 verbosestream<<"OpenALSoundManager::loadSoundData(): Writing "
466 <<"temporary file to ["<<path<<"]"<<std::endl;
467 fs::CreateAllDirs(basepath);
468 std::ofstream of(path.c_str(), std::ios::binary);
469 of.write(filedata.c_str(), filedata.size());
471 return loadSoundFile(name, path);
474 void updateListener(v3f pos, v3f vel, v3f at, v3f up)
476 m_listener_pos = pos;
477 alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
478 alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
482 alListenerfv(AL_ORIENTATION, f);
483 warn_if_error(alGetError(), "updateListener");
486 void setListenerGain(float gain)
488 alListenerf(AL_GAIN, gain);
491 int playSound(const std::string &name, bool loop, float volume)
496 SoundBuffer *buf = getFetchBuffer(name);
498 infostream<<"OpenALSoundManager: \""<<name<<"\" not found."
502 return playSoundRaw(buf, loop, volume);
504 int playSoundAt(const std::string &name, bool loop, float volume, v3f pos)
509 SoundBuffer *buf = getFetchBuffer(name);
511 infostream<<"OpenALSoundManager: \""<<name<<"\" not found."
515 return playSoundRawAt(buf, loop, volume, pos);
517 void stopSound(int sound)
522 bool soundExists(int sound)
525 return (m_sounds_playing.count(sound) != 0);
527 void updateSoundPosition(int id, v3f pos)
529 std::map<int, PlayingSound*>::iterator i =
530 m_sounds_playing.find(id);
531 if(i == m_sounds_playing.end())
533 PlayingSound *sound = i->second;
535 alSourcei(sound->source_id, AL_SOURCE_RELATIVE, false);
536 alSource3f(sound->source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
537 alSource3f(sound->source_id, AL_VELOCITY, 0, 0, 0);
538 alSourcef(sound->source_id, AL_REFERENCE_DISTANCE, 30.0);
542 ISoundManager *createOpenALSoundManager(OnDemandSoundFetcher *fetcher)
544 OpenALSoundManager *m = new OpenALSoundManager(fetcher);
545 if(m->m_is_initialized)