| 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 | |