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 | m_device(alcOpenDevice(nullptr)), |
32 | m_context(alcCreateContext(m_device, nullptr)), |
33 | m_sound_enabled(false), |
34 | m_sound_volume(0), |
35 | m_buffers(), |
36 | m_sources(), |
37 | m_update_list(), |
38 | m_music_source(), |
39 | m_music_enabled(false), |
40 | m_music_volume(0), |
41 | m_current_music() |
42 | { |
43 | try { |
44 | if (m_device == nullptr) { |
45 | throw std::runtime_error("Couldn't open audio device." ); |
46 | } |
47 | check_alc_error("Couldn't create audio context: " ); |
48 | alcMakeContextCurrent(m_context); |
49 | check_alc_error("Couldn't select audio context: " ); |
50 | |
51 | check_al_error("Audio error after init: " ); |
52 | m_sound_enabled = true; |
53 | m_music_enabled = true; |
54 | |
55 | set_listener_orientation(Vector(0.0f, 0.0f), Vector(0.0f, -1.0f)); |
56 | } catch(std::exception& e) { |
57 | if (m_context != nullptr) { |
58 | alcDestroyContext(m_context); |
59 | m_context = nullptr; |
60 | } |
61 | if (m_device != nullptr) { |
62 | alcCloseDevice(m_device); |
63 | m_device = nullptr; |
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 | m_music_source.reset(); |
73 | m_sources.clear(); |
74 | |
75 | for (const auto& buffer : m_buffers) { |
76 | alDeleteBuffers(1, &buffer.second); |
77 | } |
78 | |
79 | if (m_context != nullptr) { |
80 | alcDestroyContext(m_context); |
81 | m_context = nullptr; |
82 | } |
83 | if (m_device != nullptr) { |
84 | alcCloseDevice(m_device); |
85 | m_device = nullptr; |
86 | } |
87 | } |
88 | |
89 | ALuint |
90 | SoundManager::load_file_into_buffer(SoundFile& file) |
91 | { |
92 | ALenum format = get_sample_format(file); |
93 | ALuint buffer; |
94 | alGenBuffers(1, &buffer); |
95 | check_al_error("Couldn't create audio buffer: " ); |
96 | std::unique_ptr<char[]> samples(new char[file.m_size]); |
97 | file.read(samples.get(), file.m_size); |
98 | log_debug << "buffer: " << buffer << "\n" |
99 | << "format: " << format << "\n" |
100 | << "samples: " << samples.get() << "\n" |
101 | << "file size: " << static_cast<ALsizei>(file.m_size) << "\n" |
102 | << "file rate: " << static_cast<ALsizei>(file.m_rate) << "\n" ; |
103 | |
104 | alBufferData(buffer, format, samples.get(), |
105 | static_cast<ALsizei>(file.m_size), |
106 | static_cast<ALsizei>(file.m_rate)); |
107 | check_al_error("Couldn't fill audio buffer: " ); |
108 | |
109 | return buffer; |
110 | } |
111 | |
112 | std::unique_ptr<OpenALSoundSource> |
113 | SoundManager::intern_create_sound_source(const std::string& filename) |
114 | { |
115 | assert(m_sound_enabled); |
116 | |
117 | auto source = std::make_unique<OpenALSoundSource>(); |
118 | source->set_volume(static_cast<float>(m_sound_volume) / 100.0f); |
119 | |
120 | ALuint buffer; |
121 | |
122 | // reuse an existing static sound buffer |
123 | auto it = m_buffers.find(filename); |
124 | if (it != m_buffers.end()) { |
125 | buffer = it->second; |
126 | } else { |
127 | // Load sound file |
128 | std::unique_ptr<SoundFile> file(load_sound_file(filename)); |
129 | |
130 | if (file->m_size < 100000) { |
131 | log_debug << "Adding \"" << filename << |
132 | "\" into the buffer, file size: " << file->m_size << std::endl; |
133 | buffer = load_file_into_buffer(*file); |
134 | m_buffers.insert(std::make_pair(filename, buffer)); |
135 | } else { |
136 | log_debug << "Playing \"" << filename << |
137 | "\" as StreamSoundSource, file size: " << file->m_size << std::endl; |
138 | auto stream_source = std::make_unique<StreamSoundSource>(); |
139 | stream_source->set_sound_file(std::move(file)); |
140 | stream_source->set_volume(static_cast<float>(m_sound_volume) / 100.0f); |
141 | return std::move(stream_source); |
142 | } |
143 | } |
144 | |
145 | alSourcei(source->m_source, AL_BUFFER, buffer); |
146 | return source; |
147 | } |
148 | |
149 | std::unique_ptr<SoundSource> |
150 | SoundManager::create_sound_source(const std::string& filename) |
151 | { |
152 | if (!m_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 (!m_sound_enabled) |
167 | return; |
168 | |
169 | auto it = m_buffers.find(filename); |
170 | // already loaded? |
171 | if (it != m_buffers.end()) |
172 | return; |
173 | try { |
174 | std::unique_ptr<SoundFile> file (load_sound_file(filename)); |
175 | // only keep small files |
176 | if (file->m_size >= 100000) |
177 | return; |
178 | |
179 | ALuint buffer = load_file_into_buffer(*file); |
180 | m_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 | const float gain) |
189 | { |
190 | if (!m_sound_enabled) |
191 | return; |
192 | |
193 | // Test gain for invalid values; it must not exceed 1 because in the end |
194 | // the value is set to min(sound_gain * sound_volume, 1) |
195 | assert(gain >= 0.0f && gain <= 1.0f); |
196 | |
197 | try { |
198 | std::unique_ptr<OpenALSoundSource> source(intern_create_sound_source(filename)); |
199 | source->set_gain(gain); |
200 | |
201 | if (pos.x < 0 || pos.y < 0) { |
202 | source->set_relative(true); |
203 | } else { |
204 | source->set_position(pos); |
205 | } |
206 | source->play(); |
207 | m_sources.push_back(std::move(source)); |
208 | } catch(std::exception& e) { |
209 | log_warning << "Couldn't play sound " << filename << ": " << e.what() << std::endl; |
210 | } |
211 | } |
212 | |
213 | void |
214 | SoundManager::manage_source(std::unique_ptr<SoundSource> source) |
215 | { |
216 | assert(source); |
217 | if (dynamic_cast<OpenALSoundSource*>(source.get())) |
218 | { |
219 | std::unique_ptr<OpenALSoundSource> openal_source(dynamic_cast<OpenALSoundSource*>(source.release())); |
220 | m_sources.push_back(std::move(openal_source)); |
221 | } |
222 | } |
223 | |
224 | void |
225 | SoundManager::register_for_update(StreamSoundSource* sss) |
226 | { |
227 | if (sss) |
228 | { |
229 | m_update_list.push_back(sss); |
230 | } |
231 | } |
232 | |
233 | void |
234 | SoundManager::remove_from_update(StreamSoundSource* sss) |
235 | { |
236 | if (sss) |
237 | { |
238 | auto it = m_update_list.begin(); |
239 | while (it != m_update_list.end()) { |
240 | if (*it == sss) { |
241 | it = m_update_list.erase(it); |
242 | } else { |
243 | ++it; |
244 | } |
245 | } |
246 | } |
247 | } |
248 | |
249 | void |
250 | SoundManager::enable_sound(bool enable) |
251 | { |
252 | if (m_device == nullptr) |
253 | return; |
254 | |
255 | m_sound_enabled = enable; |
256 | } |
257 | |
258 | void |
259 | SoundManager::enable_music(bool enable) |
260 | { |
261 | if (m_device == nullptr) |
262 | return; |
263 | |
264 | m_music_enabled = enable; |
265 | if (m_music_enabled) { |
266 | play_music(m_current_music); |
267 | } else { |
268 | if (m_music_source) { |
269 | m_music_source.reset(); |
270 | } |
271 | } |
272 | } |
273 | |
274 | void |
275 | SoundManager::stop_music(float fadetime) |
276 | { |
277 | if (fadetime > 0) { |
278 | if (m_music_source |
279 | && m_music_source->get_fade_state() != StreamSoundSource::FadingOff) |
280 | m_music_source->set_fading(StreamSoundSource::FadingOff, fadetime); |
281 | } else { |
282 | m_music_source.reset(); |
283 | } |
284 | m_current_music = "" ; |
285 | } |
286 | |
287 | void |
288 | SoundManager::set_music_volume(int volume) |
289 | { |
290 | m_music_volume = volume; |
291 | if (m_music_source != nullptr) m_music_source->set_volume(static_cast<float>(volume) / 100.0f); |
292 | } |
293 | |
294 | void |
295 | SoundManager::play_music(const std::string& filename, bool fade) |
296 | { |
297 | if (filename == m_current_music && m_music_source != nullptr) |
298 | { |
299 | if (m_music_source->paused()) |
300 | { |
301 | m_music_source->resume(); |
302 | } |
303 | else if (!m_music_source->playing()) |
304 | { |
305 | m_music_source->play(); |
306 | } |
307 | return; |
308 | } |
309 | m_current_music = filename; |
310 | if (!m_music_enabled) |
311 | return; |
312 | |
313 | if (filename.empty()) { |
314 | m_music_source.reset(); |
315 | return; |
316 | } |
317 | |
318 | try { |
319 | auto newmusic = std::make_unique<StreamSoundSource>(); |
320 | newmusic->set_sound_file(load_sound_file(filename)); |
321 | newmusic->set_looping(true); |
322 | newmusic->set_relative(true); |
323 | newmusic->set_volume(static_cast<float>(m_music_volume) / 100.0f); |
324 | if (fade) |
325 | newmusic->set_fading(StreamSoundSource::FadingOn, .5f); |
326 | newmusic->play(); |
327 | |
328 | m_music_source = std::move(newmusic); |
329 | } catch(std::exception& e) { |
330 | log_warning << "Couldn't play music file '" << filename << "': " << e.what() << std::endl; |
331 | // When this happens, previous music continued playing, stop it, just in case. |
332 | stop_music(0); |
333 | } |
334 | } |
335 | |
336 | void |
337 | SoundManager::pause_music(float fadetime) |
338 | { |
339 | if (m_music_source == nullptr) |
340 | return; |
341 | |
342 | if (fadetime > 0) { |
343 | if (m_music_source |
344 | && m_music_source->get_fade_state() != StreamSoundSource::FadingPause) |
345 | m_music_source->set_fading(StreamSoundSource::FadingPause, fadetime); |
346 | } else { |
347 | m_music_source->pause(); |
348 | } |
349 | } |
350 | |
351 | void |
352 | SoundManager::pause_sounds() |
353 | { |
354 | for (auto& source : m_sources) { |
355 | if (source->playing()) { |
356 | source->pause(); |
357 | } |
358 | } |
359 | } |
360 | |
361 | void |
362 | SoundManager::resume_sounds() |
363 | { |
364 | for (auto& source : m_sources) { |
365 | if (source->paused()) { |
366 | source->resume(); |
367 | } |
368 | } |
369 | } |
370 | |
371 | void |
372 | SoundManager::stop_sounds() |
373 | { |
374 | for (auto& source : m_sources) { |
375 | source->stop(); |
376 | } |
377 | } |
378 | |
379 | void |
380 | SoundManager::set_sound_volume(int volume) |
381 | { |
382 | m_sound_volume = volume; |
383 | for (auto& source : m_sources) { |
384 | source->set_volume(static_cast<float>(volume) / 100.0f); |
385 | } |
386 | } |
387 | |
388 | void |
389 | SoundManager::resume_music(float fadetime) |
390 | { |
391 | if (m_music_source == nullptr) |
392 | return; |
393 | |
394 | if (fadetime > 0) { |
395 | if (m_music_source |
396 | && m_music_source->get_fade_state() != StreamSoundSource::FadingResume) |
397 | m_music_source->set_fading(StreamSoundSource::FadingResume, fadetime); |
398 | } else { |
399 | m_music_source->resume(); |
400 | } |
401 | } |
402 | |
403 | void |
404 | SoundManager::set_listener_position(const Vector& pos) |
405 | { |
406 | static Uint32 lastticks = SDL_GetTicks(); |
407 | |
408 | Uint32 current_ticks = SDL_GetTicks(); |
409 | if (current_ticks - lastticks < 300) |
410 | return; |
411 | lastticks = current_ticks; |
412 | |
413 | alListener3f(AL_POSITION, pos.x, pos.y, -300); |
414 | } |
415 | |
416 | void |
417 | SoundManager::set_listener_velocity(const Vector& vel) |
418 | { |
419 | alListener3f(AL_VELOCITY, vel.x, vel.y, 0); |
420 | } |
421 | |
422 | void |
423 | SoundManager::set_listener_orientation(const Vector& at, const Vector& up) |
424 | { |
425 | ALfloat orientation[]={at.x, at.y, 1.0, up.x, up.y, 0.0}; |
426 | alListenerfv(AL_ORIENTATION, orientation); |
427 | } |
428 | |
429 | void |
430 | SoundManager::update() |
431 | { |
432 | static Uint32 lasttime = SDL_GetTicks(); |
433 | Uint32 now = SDL_GetTicks(); |
434 | |
435 | if (now - lasttime < 300) |
436 | return; |
437 | lasttime = now; |
438 | |
439 | // update and check for finished sound sources |
440 | for (auto it = m_sources.begin(); it != m_sources.end(); ) { |
441 | auto& source = *it; |
442 | |
443 | source->update(); |
444 | |
445 | if (!source->playing()) { |
446 | it = m_sources.erase(it); |
447 | } else { |
448 | ++it; |
449 | } |
450 | } |
451 | // check streaming sounds |
452 | if (m_music_source) { |
453 | m_music_source->update(); |
454 | } |
455 | |
456 | if (m_context) |
457 | { |
458 | alcProcessContext(m_context); |
459 | check_alc_error("Error while processing audio context: " ); |
460 | } |
461 | |
462 | //run update() for stream_sound_source |
463 | auto s = m_update_list.begin(); |
464 | while (s != m_update_list.end()) { |
465 | (*s)->update(); |
466 | ++s; |
467 | } |
468 | } |
469 | |
470 | ALenum |
471 | SoundManager::get_sample_format(const SoundFile& file) |
472 | { |
473 | if (file.m_channels == 2) { |
474 | if (file.m_bits_per_sample == 16) { |
475 | return AL_FORMAT_STEREO16; |
476 | } else if (file.m_bits_per_sample == 8) { |
477 | return AL_FORMAT_STEREO8; |
478 | } else { |
479 | throw std::runtime_error("Only 16 and 8 bit samples supported" ); |
480 | } |
481 | } else if (file.m_channels == 1) { |
482 | if (file.m_bits_per_sample == 16) { |
483 | return AL_FORMAT_MONO16; |
484 | } else if (file.m_bits_per_sample == 8) { |
485 | return AL_FORMAT_MONO8; |
486 | } else { |
487 | throw std::runtime_error("Only 16 and 8 bit samples supported" ); |
488 | } |
489 | } |
490 | |
491 | throw std::runtime_error("Only 1 and 2 channel samples supported" ); |
492 | } |
493 | |
494 | void |
495 | SoundManager::print_openal_version() |
496 | { |
497 | log_info << "OpenAL Vendor: " << alGetString(AL_VENDOR) << std::endl; |
498 | log_info << "OpenAL Version: " << alGetString(AL_VERSION) << std::endl; |
499 | log_info << "OpenAL Renderer: " << alGetString(AL_RENDERER) << std::endl; |
500 | log_info << "OpenAl Extensions: " << alGetString(AL_EXTENSIONS) << std::endl; |
501 | } |
502 | |
503 | void |
504 | SoundManager::check_alc_error(const char* message) const |
505 | { |
506 | int err = alcGetError(m_device); |
507 | if (err != ALC_NO_ERROR) { |
508 | std::stringstream msg; |
509 | msg << message << alcGetString(m_device, err); |
510 | throw std::runtime_error(msg.str()); |
511 | } |
512 | } |
513 | |
514 | void |
515 | SoundManager::check_al_error(const char* message) |
516 | { |
517 | int err = alGetError(); |
518 | if (err != AL_NO_ERROR) { |
519 | std::stringstream msg; |
520 | msg << message << alGetString(err); |
521 | throw std::runtime_error(msg.str()); |
522 | } |
523 | } |
524 | |
525 | /* EOF */ |
526 | |