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
30SoundManager::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
70SoundManager::~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
89ALuint
90SoundManager::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
112std::unique_ptr<OpenALSoundSource>
113SoundManager::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
149std::unique_ptr<SoundSource>
150SoundManager::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
163void
164SoundManager::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
186void
187SoundManager::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
213void
214SoundManager::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
224void
225SoundManager::register_for_update(StreamSoundSource* sss)
226{
227 if (sss)
228 {
229 m_update_list.push_back(sss);
230 }
231}
232
233void
234SoundManager::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
249void
250SoundManager::enable_sound(bool enable)
251{
252 if (m_device == nullptr)
253 return;
254
255 m_sound_enabled = enable;
256}
257
258void
259SoundManager::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
274void
275SoundManager::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
287void
288SoundManager::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
294void
295SoundManager::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
336void
337SoundManager::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
351void
352SoundManager::pause_sounds()
353{
354 for (auto& source : m_sources) {
355 if (source->playing()) {
356 source->pause();
357 }
358 }
359}
360
361void
362SoundManager::resume_sounds()
363{
364 for (auto& source : m_sources) {
365 if (source->paused()) {
366 source->resume();
367 }
368 }
369}
370
371void
372SoundManager::stop_sounds()
373{
374 for (auto& source : m_sources) {
375 source->stop();
376 }
377}
378
379void
380SoundManager::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
388void
389SoundManager::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
403void
404SoundManager::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
416void
417SoundManager::set_listener_velocity(const Vector& vel)
418{
419 alListener3f(AL_VELOCITY, vel.x, vel.y, 0);
420}
421
422void
423SoundManager::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
429void
430SoundManager::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
470ALenum
471SoundManager::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
494void
495SoundManager::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
503void
504SoundManager::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
514void
515SoundManager::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