X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=src%2Faudio%2Fsound_manager.cpp;h=a5a47ae0398f775f5bba48b192a470fe0d380ac2;hb=25da12d34b356745dcf3669f234e17ee5bfa6b80;hp=bb26ed2e765901f0308586363b8e57d889805c4d;hpb=7ae3aef67ad305cb9c6ed584cdac6117da9eba88;p=supertux.git diff --git a/src/audio/sound_manager.cpp b/src/audio/sound_manager.cpp index bb26ed2e7..a5a47ae03 100644 --- a/src/audio/sound_manager.cpp +++ b/src/audio/sound_manager.cpp @@ -1,12 +1,10 @@ -// $Id: sound_manager.cpp 2334 2005-04-04 16:26:14Z grumbel $ +// SuperTux +// Copyright (C) 2006 Matthias Braun // -// SuperTux - A Jump'n Run -// Copyright (C) 2004 Matthias Braun +// along with this program. If not, see . + +#include "audio/sound_manager.hpp" -#include -#include -#include +#include +#include #include #include -#include - -#include "audio/sound_manager.h" - -#include "audio/musicref.h" -#include "physfs/physfs_sdl.h" -#include "moving_object.h" -#include "resources.h" - -SoundManager::SoundManager() - : current_music(0), m_music_enabled(true) , m_sound_enabled(true), - audio_device(false) +#include + +#include "audio/dummy_sound_source.hpp" +#include "audio/sound_file.hpp" +#include "audio/stream_sound_source.hpp" +#include "util/log.hpp" + +#ifndef DEBUG +/** Older openal versions often miss this function and it isn't that vital for + * supertux... + */ +#ifdef alcGetString +#undef alcGetString +#endif +#define alcGetString(x,y) "" +#endif + +SoundManager* sound_manager = 0; + +SoundManager::SoundManager() : + device(0), + context(0), + sound_enabled(false), + buffers(), + sources(), + update_list(), + music_source(0), + music_enabled(false), + current_music() { + try { + device = alcOpenDevice(0); + if (device == NULL) { + throw std::runtime_error("Couldn't open audio device."); + } + + int attributes[] = { 0 }; + context = alcCreateContext(device, attributes); + check_alc_error("Couldn't create audio context: "); + alcMakeContextCurrent(context); + check_alc_error("Couldn't select audio context: "); + + check_al_error("Audio error after init: "); + sound_enabled = true; + music_enabled = true; + } catch(std::exception& e) { + if(context != NULL) + alcDestroyContext(context); + context = NULL; + if(device != NULL) + alcCloseDevice(device); + device = NULL; + log_warning << "Couldn't initialize audio device: " << e.what() << std::endl; + print_openal_version(); + } } SoundManager::~SoundManager() { - for(Sounds::iterator i = sounds.begin(); i != sounds.end(); ++i) { - Mix_FreeChunk(i->second); + delete music_source; + + for(SoundSources::iterator i = sources.begin(); i != sources.end(); ++i) { + delete *i; } - sounds.clear(); -} -int -SoundManager::play_sound(const std::string& name,int loops) -{ - if(!audio_device || !m_sound_enabled) - return -1; - - Mix_Chunk* chunk = preload_sound(name); - if(chunk == 0) { - std::cerr << "Sound '" << name << "' not found.\n"; - return -1; + for(SoundBuffers::iterator i = buffers.begin(); i != buffers.end(); ++i) { + ALuint buffer = i->second; + alDeleteBuffers(1, &buffer); } - int chan=Mix_PlayChannel(-1, chunk, loops); - Mix_Volume(chan,MIX_MAX_VOLUME); - return chan; -} + if(context != NULL) { + alcDestroyContext(context); + } + if(device != NULL) { + alcCloseDevice(device); + } +} -int -SoundManager::play_sound(const std::string& sound, const MovingObject* object, - const Vector& pos) +ALuint +SoundManager::load_file_into_buffer(SoundFile* file) { - // TODO keep track of the object later and move the sound along with the - // object. - return play_sound(sound, object->get_pos(), pos); + ALenum format = get_sample_format(file); + ALuint buffer; + alGenBuffers(1, &buffer); + check_al_error("Couldn't create audio buffer: "); + char* samples = new char[file->size]; + try { + file->read(samples, file->size); + alBufferData(buffer, format, samples, + static_cast (file->size), + static_cast (file->rate)); + check_al_error("Couldn't fill audio buffer: "); + } catch(...) { + delete[] samples; + throw; + } + delete[] samples; + + return buffer; } -int -SoundManager::play_sound(const std::string& sound, const Vector& pos, - const Vector& pos2) +OpenALSoundSource* +SoundManager::intern_create_sound_source(const std::string& filename) { - if(!audio_device || !m_sound_enabled) - return -1; + if(!sound_enabled) + throw std::runtime_error("sound disabled"); - Mix_Chunk* chunk = preload_sound(sound); - if(chunk == 0) { - std::cerr << "Sound '" << sound << "' not found.\n"; - return -1; - } + std::auto_ptr source (new OpenALSoundSource()); - // TODO make sure this formula is good - float distance - = pos2.x- pos.x; - int loud = int(255.0/float(1600) * fabsf(distance)); - if(loud > 255) - return -1; + ALuint buffer; - int chan = Mix_PlayChannel(-1, chunk, 0); - if(chan < 0) - return -1; - Mix_Volume(chan,MIX_MAX_VOLUME); - Mix_SetDistance(chan, loud); + // reuse an existing static sound buffer + SoundBuffers::iterator i = buffers.find(filename); + if(i != buffers.end()) { + buffer = i->second; + } else { + // Load sound file + std::auto_ptr file (load_sound_file(filename)); + + if(file->size < 100000) { + buffer = load_file_into_buffer(file.get()); + buffers.insert(std::make_pair(filename, buffer)); + } else { + StreamSoundSource* source = new StreamSoundSource(); + source->set_sound_file(file.release()); + return source; + } + + log_debug << "Uncached sound \"" << filename << "\" requested to be played" << std::endl; + } - // very bad way to do this... - if(distance > 100) - Mix_SetPanning(chan, 230, 24); - else if(distance < -100) - Mix_SetPanning(chan, 24, 230); - return chan; + alSourcei(source->source, AL_BUFFER, buffer); + return source.release(); } -MusicRef -SoundManager::load_music(const std::string& file) +SoundSource* +SoundManager::create_sound_source(const std::string& filename) { - if(!audio_device) - return MusicRef(0); - - if(!exists_music(file)) { - std::stringstream msg; - msg << "Couldn't load musicfile '" << file << "': " << SDL_GetError(); - throw std::runtime_error(msg.str()); + if(!sound_enabled) + return create_dummy_sound_source(); + + try { + return intern_create_sound_source(filename); + } catch(std::exception &e) { + log_warning << "Couldn't create audio source: " << e.what() << std::endl; + return create_dummy_sound_source(); } - - std::map::iterator i = musics.find(file); - assert(i != musics.end()); - return MusicRef(& (i->second)); } -bool -SoundManager::exists_music(const std::string& filename) +void +SoundManager::preload(const std::string& filename) { - if(!audio_device) - return true; - - // song already loaded? - std::map::iterator i = musics.find(filename); - if(i != musics.end()) { - return true; + if(!sound_enabled) + return; + + SoundBuffers::iterator i = buffers.find(filename); + // already loaded? + if(i != buffers.end()) + return; + try { + std::auto_ptr file (load_sound_file(filename)); + // only keep small files + if(file->size >= 100000) + return; + + ALuint buffer = load_file_into_buffer(file.get()); + buffers.insert(std::make_pair(filename, buffer)); + } catch(std::exception& e) { + log_warning << "Error while preloading sound file: " << e.what() << std::endl; } - - const char* dir = PHYSFS_getRealDir(filename.c_str()); - if(dir == 0) - return false; - Mix_Music* song = Mix_LoadMUS( (std::string(dir) + "/" + filename).c_str() ); - if(song == 0) - return false; +} - // insert into music list - std::pair::iterator, bool> result = - musics.insert( - std::make_pair (filename, MusicResource())); - MusicResource& resource = result.first->second; - resource.manager = this; - resource.music = song; +void +SoundManager::play(const std::string& filename, const Vector& pos) +{ + if(!sound_enabled) + return; - return true; + try { + std::auto_ptr source + (intern_create_sound_source(filename)); + + if(pos == Vector(-1, -1)) { + source->set_rollof_factor(0); + } else { + source->set_position(pos); + } + source->play(); + sources.push_back(source.release()); + } catch(std::exception& e) { + log_warning << "Couldn't play sound " << filename << ": " << e.what() << std::endl; + } } void -SoundManager::free_music(MusicResource* ) +SoundManager::manage_source(SoundSource* source) { - // TODO free music, currently we can't do this since SDL_mixer seems to have - // some bugs if you load/free alot of mod files. + assert(source != NULL); + + OpenALSoundSource* openal_source = dynamic_cast (source); + if(openal_source != NULL) { + sources.push_back(openal_source); + } +} + +void +SoundManager::register_for_update( StreamSoundSource* sss ){ + if( sss != NULL ){ + update_list.push_back( sss ); + } } void -SoundManager::play_music(const MusicRef& musicref, int loops) +SoundManager::remove_from_update( StreamSoundSource* sss ){ + if( sss != NULL ){ + StreamSoundSources::iterator i = update_list.begin(); + while( i != update_list.end() ){ + if( *i == sss ){ + i = update_list.erase(i); + } else { + i++; + } + } + } +} + +void +SoundManager::enable_sound(bool enable) { - if(!audio_device) + if(device == NULL) return; - if(musicref.music == 0 || current_music == musicref.music) + sound_enabled = enable; +} + +void +SoundManager::enable_music(bool enable) +{ + if(device == NULL) return; - if(current_music) - current_music->refcount--; - - current_music = musicref.music; - current_music->refcount++; - - if(m_music_enabled) - Mix_PlayMusic(current_music->music, loops); + music_enabled = enable; + if(music_enabled) { + play_music(current_music); + } else { + if(music_source) { + delete music_source; + music_source = 0; + } + } } void -SoundManager::halt_music() +SoundManager::stop_music(float fadetime) { - if(!audio_device) - return; - - Mix_HaltMusic(); - - if(current_music) { - current_music->refcount--; - if(current_music->refcount == 0) - free_music(current_music); - current_music = 0; + if(fadetime > 0) { + if(music_source + && music_source->get_fade_state() != StreamSoundSource::FadingOff) + music_source->set_fading(StreamSoundSource::FadingOff, fadetime); + } else { + delete music_source; + music_source = NULL; } + current_music = ""; } void -SoundManager::enable_music(bool enable) +SoundManager::play_music(const std::string& filename, bool fade) { - if(!audio_device) + if(filename == current_music && music_source != NULL) + return; + current_music = filename; + if(!music_enabled) return; - if(enable == m_music_enabled) + if(filename == "") { + delete music_source; + music_source = NULL; return; - - m_music_enabled = enable; - if(m_music_enabled == false) { - Mix_HaltMusic(); - } else { - if(current_music) - Mix_PlayMusic(current_music->music, -1); + } + + try { + std::auto_ptr newmusic (new StreamSoundSource()); + alSourcef(newmusic->source, AL_ROLLOFF_FACTOR, 0); + newmusic->set_sound_file(load_sound_file(filename)); + newmusic->set_looping(true); + if(fade) + newmusic->set_fading(StreamSoundSource::FadingOn, .5f); + newmusic->play(); + + delete music_source; + music_source = newmusic.release(); + } catch(std::exception& e) { + log_warning << "Couldn't play music file '" << filename << "': " << e.what() << std::endl; } } void -SoundManager::enable_sound(bool enable) +SoundManager::set_listener_position(const Vector& pos) { - if(!audio_device) + static Uint32 lastticks = SDL_GetTicks(); + + Uint32 current_ticks = SDL_GetTicks(); + if(current_ticks - lastticks < 300) return; - - m_sound_enabled = enable; + lastticks = current_ticks; + + alListener3f(AL_POSITION, pos.x, pos.y, 0); } -SoundManager::MusicResource::~MusicResource() +void +SoundManager::set_listener_velocity(const Vector& vel) { - // don't free music buggy SDL_Mixer crashs for some mod files - // Mix_FreeMusic(music); + alListener3f(AL_VELOCITY, vel.x, vel.y, 0); } -Mix_Chunk* SoundManager::preload_sound(const std::string& name) +void +SoundManager::update() { - if(!audio_device) - return 0; + static Uint32 lasttime = SDL_GetTicks(); + Uint32 now = SDL_GetTicks(); + + if(now - lasttime < 300) + return; + lasttime = now; - Sounds::iterator i = sounds.find(name); - if(i != sounds.end()) { - return i->second; + // update and check for finished sound sources + for(SoundSources::iterator i = sources.begin(); i != sources.end(); ) { + OpenALSoundSource* source = *i; + + source->update(); + + if(!source->playing()) { + delete source; + i = sources.erase(i); + } else { + ++i; + } + } + // check streaming sounds + if(music_source) { + music_source->update(); } - std::string filename = "sounds/"; - filename += name; - filename += ".wav"; - - Mix_Chunk* chunk = Mix_LoadWAV_RW(get_physfs_SDLRWops(filename), true); - if(chunk != 0) { - sounds.insert(std::make_pair(name, chunk)); + if (context) + { + alcProcessContext(context); + check_alc_error("Error while processing audio context: "); } - return chunk; + //run update() for stream_sound_source + StreamSoundSources::iterator s = update_list.begin(); + while( s != update_list.end() ){ + (*s)->update(); + s++; + } +} + +ALenum +SoundManager::get_sample_format(SoundFile* file) +{ + if(file->channels == 2) { + if(file->bits_per_sample == 16) { + return AL_FORMAT_STEREO16; + } else if(file->bits_per_sample == 8) { + return AL_FORMAT_STEREO8; + } else { + throw std::runtime_error("Only 16 and 8 bit samples supported"); + } + } else if(file->channels == 1) { + if(file->bits_per_sample == 16) { + return AL_FORMAT_MONO16; + } else if(file->bits_per_sample == 8) { + return AL_FORMAT_MONO8; + } else { + throw std::runtime_error("Only 16 and 8 bit samples supported"); + } + } + + throw std::runtime_error("Only 1 and 2 channel samples supported"); +} + +void +SoundManager::print_openal_version() +{ + log_info << "OpenAL Vendor: " << alGetString(AL_VENDOR) << std::endl; + log_info << "OpenAL Version: " << alGetString(AL_VERSION) << std::endl; + log_info << "OpenAL Renderer: " << alGetString(AL_RENDERER) << std::endl; + log_info << "OpenAl Extensions: " << alGetString(AL_EXTENSIONS) << std::endl; +} + +void +SoundManager::check_alc_error(const char* message) +{ + int err = alcGetError(device); + if(err != ALC_NO_ERROR) { + std::stringstream msg; + msg << message << alcGetString(device, err); + throw std::runtime_error(msg.str()); + } +} + +void +SoundManager::check_al_error(const char* message) +{ + int err = alGetError(); + if(err != AL_NO_ERROR) { + std::stringstream msg; + msg << message << alGetString(err); + throw std::runtime_error(msg.str()); + } } +/* EOF */