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