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