-// $Id: sound_manager.cpp 2334 2005-04-04 16:26:14Z grumbel $
+// $Id$
//
-// SuperTux - A Jump'n Run
-// Copyright (C) 2004 Matthias Braun <matze@braunis.de
+// SuperTux
+// Copyright (C) 2006 Matthias Braun <matze@braunis.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <config.h>
-#include <cmath>
-#include <cassert>
-#include <iostream>
+#include "sound_manager.hpp"
+
#include <stdexcept>
+#include <iostream>
#include <sstream>
-
-#include "audio/sound_manager.h"
-#include "audio/musicref.h"
-#include "moving_object.h"
-#include "resources.h"
+#include <memory>
+#include <assert.h>
+#include <SDL.h>
+
+#include "sound_file.hpp"
+#include "sound_source.hpp"
+#include "openal_sound_source.hpp"
+#include "stream_sound_source.hpp"
+#include "dummy_sound_source.hpp"
+#include "log.hpp"
+#include "timer.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()
- : current_music(0), m_music_enabled(true) , m_sound_enabled(true),
- audio_device(false)
+ : device(0), context(0), sound_enabled(false), music_source(0),
+ music_enabled(false)
{
+ 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();
-}
-void
-SoundManager::play_sound(const std::string& name)
-{
- if(!audio_device || !m_sound_enabled)
- return;
-
- Mix_Chunk* chunk = preload_sound(name);
- if(chunk == 0) {
- std::cerr << "Sound '" << name << "' not found.\n";
- return;
+ for(SoundBuffers::iterator i = buffers.begin(); i != buffers.end(); ++i) {
+ ALuint buffer = i->second;
+ alDeleteBuffers(1, &buffer);
}
- Mix_PlayChannel(-1, chunk, 0);
-}
-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;
+ if(context != NULL) {
+ alcDestroyContext(context);
+ }
+ if(device != NULL) {
+ alcCloseDevice(device);
}
- return Mix_PlayChannel(-1, chunk, loops);
}
-void
-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.
- 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<ALsizei> (file->size),
+ static_cast<ALsizei> (file->rate));
+ check_al_error("Couldn't fill audio buffer: ");
+ } catch(...) {
+ delete[] samples;
+ throw;
+ }
+ delete[] samples;
+
+ return buffer;
}
-void
-SoundManager::play_sound(const std::string& sound, const Vector& pos,
- const Vector& pos2)
+SoundSource*
+SoundManager::create_sound_source(const std::string& filename)
{
- if(!audio_device || !m_sound_enabled)
- return;
-
- Mix_Chunk* chunk = preload_sound(sound);
- if(chunk == 0) {
- std::cerr << "Sound '" << sound << "' not found.\n";
- return;
+ if(!sound_enabled)
+ return create_dummy_sound_source();
+
+ std::auto_ptr<OpenALSoundSource> source;
+ try {
+ source.reset(new OpenALSoundSource());
+ } catch(std::exception& e) {
+ log_warning << "Couldn't create audio source: " << e.what() << std::endl;
+ return create_dummy_sound_source();
}
- // 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;
+ ALuint buffer;
- int chan = Mix_PlayChannel(-1, chunk, 0);
- if(chan < 0)
- return;
- Mix_SetDistance(chan, loud);
+ // reuse an existing static sound buffer
+ SoundBuffers::iterator i = buffers.find(filename);
+ if(i != buffers.end()) {
+ buffer = i->second;
+ } else {
+ try {
+ // Load sound file
+ std::auto_ptr<SoundFile> 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;
+ }
+ } catch(std::exception& e) {
+ log_warning << "Couldn't load soundfile '" << filename << "': " << e.what() << std::endl;
+ return create_dummy_sound_source();
+ }
+ }
- // very bad way to do this...
- if(distance > 100)
- Mix_SetPanning(chan, 230, 24);
- else if(distance < -100)
- Mix_SetPanning(chan, 24, 230);
+ alSourcei(source->source, AL_BUFFER, buffer);
+ return source.release();
}
-// Register a sound effect function - basti_
-
-void
-SoundManager::register_effect(int channel,Mix_EffectFunc_t f,
- Mix_EffectDone_t d,void * arg) {
-
- if(!audio_device || !m_sound_enabled)
+void
+SoundManager::preload(const std::string& filename)
+{
+ if(!sound_enabled)
return;
- Mix_RegisterEffect(channel,f,d,arg);
-}
-
-// Adjust the Volume of a channel "on line". Needs sizeof(float) static data.
-#define __ATYPE__ signed short int
+ SoundBuffers::iterator i = buffers.find(filename);
+ // already loaded?
+ if(i != buffers.end())
+ return;
-void
-SoundManager::volume_adjust(int chan, void *stream, int len, void *udata) {
- ((float *)udata)[1]=((float *)udata)[1]*0.95+
- (((float *)udata)[0]-
- ((float *)udata)[1])*0.05; // decay towards [0] - declick
- float vol=((float*)udata)[1];
+ std::auto_ptr<SoundFile> file (load_sound_file(filename));
+ // only keep small files
+ if(file->size >= 100000)
+ return;
- for (int i=0;i<len/2;i++)
- ((__ATYPE__ *)stream)[i]=
- ((__ATYPE__)(((signed short int *)stream)[i]*vol));
- // FIXME: This should be audio-type dependant - how to do this correctly?
-
- chan=0; // -Werror sucks
+ ALuint buffer = load_file_into_buffer(file.get());
+ buffers.insert(std::make_pair(filename, buffer));
}
+void
+SoundManager::play(const std::string& filename, const Vector& pos)
+{
+ if(!sound_enabled)
+ return;
+ try {
+ std::auto_ptr<OpenALSoundSource> source
+ (static_cast<OpenALSoundSource*> (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;
+ }
+}
-MusicRef
-SoundManager::load_music(const std::string& file)
+void
+SoundManager::manage_source(SoundSource* source)
{
- if(!audio_device)
- return MusicRef(0);
+ assert(source != NULL);
- if(!exists_music(file)) {
- std::stringstream msg;
- msg << "Couldn't load musicfile '" << file << "': " << SDL_GetError();
- throw std::runtime_error(msg.str());
+ OpenALSoundSource* openal_source = dynamic_cast<OpenALSoundSource*> (source);
+ if(openal_source != NULL) {
+ sources.push_back(openal_source);
}
-
- std::map<std::string, MusicResource>::iterator i = musics.find(file);
- assert(i != musics.end());
- return MusicRef(& (i->second));
}
-bool
-SoundManager::exists_music(const std::string& file)
-{
- if(!audio_device)
- return true;
-
- // song already loaded?
- std::map<std::string, MusicResource>::iterator i = musics.find(file);
- if(i != musics.end()) {
- return true;
+void
+SoundManager::register_for_update( StreamSoundSource* sss ){
+ if( sss != NULL ){
+ update_list.push_back( sss );
}
-
- Mix_Music* song = Mix_LoadMUS(file.c_str());
- if(song == 0)
- return false;
-
- // insert into music list
- std::pair<std::map<std::string, MusicResource>::iterator, bool> result =
- musics.insert(
- std::make_pair<std::string, MusicResource> (file, MusicResource()));
- MusicResource& resource = result.first->second;
- resource.manager = this;
- resource.music = song;
-
- return true;
}
void
-SoundManager::free_music(MusicResource* )
-{
- // 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.
+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::play_music(const MusicRef& musicref, int loops)
+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<StreamSoundSource> 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;
+
+ // 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();
+ }
+
+ if (context)
+ {
+ alcProcessContext(context);
+ check_alc_error("Error while processing audio context: ");
+ }
- Sounds::iterator i = sounds.find(name);
- if(i != sounds.end()) {
- return i->second;
+ //run update() for stream_sound_source
+ StreamSoundSources::iterator s = update_list.begin();
+ while( s != update_list.end() ){
+ (*s)->update();
+ s++;
}
+}
- std::string filename = "sounds/";
- filename += name;
- filename += ".wav";
- filename = get_resource_filename(filename);
-
- Mix_Chunk* chunk = Mix_LoadWAV(filename.c_str());
- if(chunk != 0) {
- sounds.insert(std::make_pair(name, chunk));
+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");
+ }
}
- return chunk;
+ 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());
+ }
+}