Move some more globals to supertux/globals.hpp
[supertux.git] / src / audio / sound_manager.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "audio/sound_manager.hpp"
18
19 #include <SDL.h>
20 #include <assert.h>
21 #include <stdexcept>
22 #include <sstream>
23 #include <memory>
24
25 #include "audio/dummy_sound_source.hpp"
26 #include "audio/sound_file.hpp"
27 #include "audio/stream_sound_source.hpp"
28 #include "util/log.hpp"
29
30 #ifdef NDEBUG
31 /** Older openal versions often miss this function and it isn't that vital for
32  * supertux...
33  */
34 #ifdef alcGetString
35 #  undef alcGetString
36 #endif
37 #  define alcGetString(x,y) ""
38 #endif
39
40 SoundManager::SoundManager() :
41   device(0), 
42   context(0), 
43   sound_enabled(false), 
44   buffers(),
45   sources(),
46   update_list(),
47   music_source(0),
48   music_enabled(false),
49   current_music()
50 {
51   try {
52     device = alcOpenDevice(0);
53     if (device == NULL) {
54       throw std::runtime_error("Couldn't open audio device.");
55     }
56
57     int attributes[] = { 0 };
58     context = alcCreateContext(device, attributes);
59     check_alc_error("Couldn't create audio context: ");
60     alcMakeContextCurrent(context);
61     check_alc_error("Couldn't select audio context: ");
62
63     check_al_error("Audio error after init: ");
64     sound_enabled = true;
65     music_enabled = true;
66   } catch(std::exception& e) {
67     if(context != NULL)
68       alcDestroyContext(context);
69     context = NULL;
70     if(device != NULL)
71       alcCloseDevice(device);
72     device = NULL;
73     log_warning << "Couldn't initialize audio device: " << e.what() << std::endl;
74     print_openal_version();
75   }
76 }
77
78 SoundManager::~SoundManager()
79 {
80   delete music_source;
81
82   for(SoundSources::iterator i = sources.begin(); i != sources.end(); ++i) {
83     delete *i;
84   }
85
86   for(SoundBuffers::iterator i = buffers.begin(); i != buffers.end(); ++i) {
87     ALuint buffer = i->second;
88     alDeleteBuffers(1, &buffer);
89   }
90
91   if(context != NULL) {
92     alcDestroyContext(context);
93   }
94   if(device != NULL) {
95     alcCloseDevice(device);
96   }
97 }
98
99 ALuint
100 SoundManager::load_file_into_buffer(SoundFile* file)
101 {
102   ALenum format = get_sample_format(file);
103   ALuint buffer;
104   alGenBuffers(1, &buffer);
105   check_al_error("Couldn't create audio buffer: ");
106   char* samples = new char[file->size];
107   try {
108     file->read(samples, file->size);
109     alBufferData(buffer, format, samples,
110                  static_cast<ALsizei> (file->size),
111                  static_cast<ALsizei> (file->rate));
112     check_al_error("Couldn't fill audio buffer: ");
113   } catch(...) {
114     delete[] samples;
115     throw;
116   }
117   delete[] samples;
118
119   return buffer;
120 }
121
122 OpenALSoundSource*
123 SoundManager::intern_create_sound_source(const std::string& filename)
124 {
125   if(!sound_enabled)
126     throw std::runtime_error("sound disabled");
127
128   std::auto_ptr<OpenALSoundSource> source (new OpenALSoundSource());
129
130   ALuint buffer;
131
132   // reuse an existing static sound buffer
133   SoundBuffers::iterator i = buffers.find(filename);
134   if(i != buffers.end()) {
135     buffer = i->second;
136   } else {
137     // Load sound file
138     std::auto_ptr<SoundFile> file (load_sound_file(filename));
139
140     if(file->size < 100000) {
141       buffer = load_file_into_buffer(file.get());
142       buffers.insert(std::make_pair(filename, buffer));
143     } else {
144       StreamSoundSource* source = new StreamSoundSource();
145       source->set_sound_file(file.release());
146       return source;
147     }
148
149     log_debug << "Uncached sound \"" << filename << "\" requested to be played" << std::endl;
150   }
151
152   alSourcei(source->source, AL_BUFFER, buffer);
153   return source.release();
154 }
155
156 SoundSource*
157 SoundManager::create_sound_source(const std::string& filename)
158 {
159   if(!sound_enabled)
160     return create_dummy_sound_source();
161
162   try {
163     return intern_create_sound_source(filename);
164   } catch(std::exception &e) {
165     log_warning << "Couldn't create audio source: " << e.what() << std::endl;
166     return create_dummy_sound_source();
167   }
168 }
169
170 void
171 SoundManager::preload(const std::string& filename)
172 {
173   if(!sound_enabled)
174     return;
175
176   SoundBuffers::iterator i = buffers.find(filename);
177   // already loaded?
178   if(i != buffers.end())
179     return;
180   try {
181     std::auto_ptr<SoundFile> file (load_sound_file(filename));
182     // only keep small files
183     if(file->size >= 100000)
184       return;
185
186     ALuint buffer = load_file_into_buffer(file.get());
187     buffers.insert(std::make_pair(filename, buffer));
188   } catch(std::exception& e) {
189     log_warning << "Error while preloading sound file: " << e.what() << std::endl;
190   }
191 }
192
193 void
194 SoundManager::play(const std::string& filename, const Vector& pos)
195 {
196   if(!sound_enabled)
197     return;
198
199   try {
200     std::auto_ptr<OpenALSoundSource> source
201       (intern_create_sound_source(filename));
202
203     if(pos == Vector(-1, -1)) {
204       source->set_rollof_factor(0);
205     } else {
206       source->set_position(pos);
207     }
208     source->play();
209     sources.push_back(source.release());
210   } catch(std::exception& e) {
211     log_warning << "Couldn't play sound " << filename << ": " << e.what() << std::endl;
212   }
213 }
214
215 void
216 SoundManager::manage_source(SoundSource* source)
217 {
218   assert(source != NULL);
219
220   OpenALSoundSource* openal_source = dynamic_cast<OpenALSoundSource*> (source);
221   if(openal_source != NULL) {
222     sources.push_back(openal_source);
223   }
224 }
225
226 void
227 SoundManager::register_for_update( StreamSoundSource* sss ){
228   if( sss != NULL ){
229     update_list.push_back( sss );
230   }
231 }
232
233 void
234 SoundManager::remove_from_update( StreamSoundSource* sss  ){
235   if( sss != NULL ){
236     StreamSoundSources::iterator i = update_list.begin();
237     while( i != update_list.end() ){
238       if( *i == sss ){
239         i = update_list.erase(i);
240       } else {
241         i++;
242       }
243     }
244   }
245 }
246
247 void
248 SoundManager::enable_sound(bool enable)
249 {
250   if(device == NULL)
251     return;
252
253   sound_enabled = enable;
254 }
255
256 void
257 SoundManager::enable_music(bool enable)
258 {
259   if(device == NULL)
260     return;
261
262   music_enabled = enable;
263   if(music_enabled) {
264     play_music(current_music);
265   } else {
266     if(music_source) {
267       delete music_source;
268       music_source = 0;
269     }
270   }
271 }
272
273 void
274 SoundManager::stop_music(float fadetime)
275 {
276   if(fadetime > 0) {
277     if(music_source
278        && music_source->get_fade_state() != StreamSoundSource::FadingOff)
279       music_source->set_fading(StreamSoundSource::FadingOff, fadetime);
280   } else {
281     delete music_source;
282     music_source = NULL;
283   }
284   current_music = "";
285 }
286
287 void
288 SoundManager::play_music(const std::string& filename, bool fade)
289 {
290   if(filename == current_music && music_source != NULL)
291     return;
292   current_music = filename;
293   if(!music_enabled)
294     return;
295
296   if(filename == "") {
297     delete music_source;
298     music_source = NULL;
299     return;
300   }
301
302   try {
303     std::auto_ptr<StreamSoundSource> newmusic (new StreamSoundSource());
304     alSourcef(newmusic->source, AL_ROLLOFF_FACTOR, 0);
305     newmusic->set_sound_file(load_sound_file(filename));
306     newmusic->set_looping(true);
307     if(fade)
308       newmusic->set_fading(StreamSoundSource::FadingOn, .5f);
309     newmusic->play();
310
311     delete music_source;
312     music_source = newmusic.release();
313   } catch(std::exception& e) {
314     log_warning << "Couldn't play music file '" << filename << "': " << e.what() << std::endl;
315   }
316 }
317
318 void
319 SoundManager::set_listener_position(const Vector& pos)
320 {
321   static Uint32 lastticks = SDL_GetTicks();
322
323   Uint32 current_ticks = SDL_GetTicks();
324   if(current_ticks - lastticks < 300)
325     return;
326   lastticks = current_ticks;
327
328   alListener3f(AL_POSITION, pos.x, pos.y, 0);
329 }
330
331 void
332 SoundManager::set_listener_velocity(const Vector& vel)
333 {
334   alListener3f(AL_VELOCITY, vel.x, vel.y, 0);
335 }
336
337 void
338 SoundManager::update()
339 {
340   static Uint32 lasttime = SDL_GetTicks();
341   Uint32 now = SDL_GetTicks();
342
343   if(now - lasttime < 300)
344     return;
345   lasttime = now;
346
347   // update and check for finished sound sources
348   for(SoundSources::iterator i = sources.begin(); i != sources.end(); ) {
349     OpenALSoundSource* source = *i;
350
351     source->update();
352
353     if(!source->playing()) {
354       delete source;
355       i = sources.erase(i);
356     } else {
357       ++i;
358     }
359   }
360   // check streaming sounds
361   if(music_source) {
362     music_source->update();
363   }
364
365   if (context)
366   {
367     alcProcessContext(context);
368     check_alc_error("Error while processing audio context: ");
369   }
370
371   //run update() for stream_sound_source
372   StreamSoundSources::iterator s = update_list.begin();
373   while( s != update_list.end() ){
374     (*s)->update();
375     s++;
376   }
377 }
378
379 ALenum
380 SoundManager::get_sample_format(SoundFile* file)
381 {
382   if(file->channels == 2) {
383     if(file->bits_per_sample == 16) {
384       return AL_FORMAT_STEREO16;
385     } else if(file->bits_per_sample == 8) {
386       return AL_FORMAT_STEREO8;
387     } else {
388       throw std::runtime_error("Only 16 and 8 bit samples supported");
389     }
390   } else if(file->channels == 1) {
391     if(file->bits_per_sample == 16) {
392       return AL_FORMAT_MONO16;
393     } else if(file->bits_per_sample == 8) {
394       return AL_FORMAT_MONO8;
395     } else {
396       throw std::runtime_error("Only 16 and 8 bit samples supported");
397     }
398   }
399
400   throw std::runtime_error("Only 1 and 2 channel samples supported");
401 }
402
403 void
404 SoundManager::print_openal_version()
405 {
406   log_info << "OpenAL Vendor: " << alGetString(AL_VENDOR) << std::endl;
407   log_info << "OpenAL Version: " << alGetString(AL_VERSION) << std::endl;
408   log_info << "OpenAL Renderer: " << alGetString(AL_RENDERER) << std::endl;
409   log_info << "OpenAl Extensions: " << alGetString(AL_EXTENSIONS) << std::endl;
410 }
411
412 void
413 SoundManager::check_alc_error(const char* message)
414 {
415   int err = alcGetError(device);
416   if(err != ALC_NO_ERROR) {
417     std::stringstream msg;
418     msg << message << alcGetString(device, err);
419     throw std::runtime_error(msg.str());
420   }
421 }
422
423 void
424 SoundManager::check_al_error(const char* message)
425 {
426   int err = alGetError();
427   if(err != AL_NO_ERROR) {
428     std::stringstream msg;
429     msg << message << alGetString(err);
430     throw std::runtime_error(msg.str());
431   }
432 }
433
434 /* EOF */