/* $Id: audio.cpp,v 1.37 2005/12/19 12:11:53 chfreund Exp $ */ /******************************************************************************/ #include #include #include #include #include #include #include #include "global.hpp" #include "audio.hpp" #include "loader.hpp" #include "wopsettings.hpp" #include "random.hpp" #include "string.hpp" /******************************************************************************/ Audio* Audio::m_instance = 0; bool Audio::m_quiet = true; bool Audio::m_playNextTrack = false, Audio::m_inJukeboxMode = false; int Audio::m_listeningPositionX = 0; int Audio::m_listeningPositionY = 0; static double lowPassFilterVal = 0.0; bool Audio::m_lowPassFilterEnabled = false; FILE* Audio::m_outputFile = 0; std::vector Audio::m_track; Mix_Music* Audio::m_currentTrack = 0; /******************************************************************************/ #define QUIET_RETURN if( m_quiet ) return /******************************************************************************/ bool initLowPassFilterBuffer( int* buffer, const int bufferLength ) { for( int i = 0; i < bufferLength; i++ ) buffer[i] = 0; return true; } void lowPassFilter( int channel, void* stream, int len, void* udata ) { const int bufferLength = 32; static int buffer[bufferLength]; static const bool bufferInitialized = initLowPassFilterBuffer( buffer, bufferLength ); static int bufferPos = 0; static int meanL = 0, meanR = 0; int* sample = reinterpret_cast( stream ); Sint32 origSampleL, origSampleR; len /= 4; for( int samplePos = 0; samplePos < len; samplePos++ ) { origSampleL = sample[samplePos] >> 16; meanL += origSampleL - buffer[bufferPos]; buffer[bufferPos] = origSampleL; //buffer[bufferPos] = ECHO * buffer[bufferPos] + (1.0 - ECHO) * newSample; bufferPos = (bufferPos + 1) % bufferLength; origSampleR = (sample[samplePos] << 16) >> 16; meanR += origSampleR - buffer[bufferPos]; buffer[bufferPos] = origSampleR; //buffer[bufferPos] = ECHO * buffer[bufferPos] + (1.0 - ECHO) * newSample; bufferPos = (bufferPos + 1) % bufferLength; const int newSampleL = int(lowPassFilterVal*meanL + (1.0-lowPassFilterVal)*(origSampleL<<4)); const int newSampleR = int(lowPassFilterVal*meanR + (1.0-lowPassFilterVal)*(origSampleR<<4)); sample[samplePos] = (((newSampleL>>4) & 0x0000ffff) << 16) + ((newSampleR>>4) & 0x0000ffff); } } /******************************************************************************/ Audio::Audio() : m_isRecording( false ) { WopSettings* settings = WopSettings::getInstance(); m_quiet = settings->getQuiet(); QUIET_RETURN; LOG( 1 ) INFO( "Audio::Audio: Initializing audio\n" ); m_quiet = true; #ifdef WIN32 m_chunksize = 1024; #else m_chunksize = 1024; #endif if( ! CHECK( ! SDL_InitSubSystem( SDL_INIT_AUDIO ), "Audio::Audio: Couldn't initialize audio\n" ) ) return; if( ! CHECK( ! Mix_OpenAudio( 22050, AUDIO_S16SYS, 2, m_chunksize ), "Audio::Audio: Couldn't open audio device\n") ) return; INFO( "Audio::Audio: allocated %i channels\n", Mix_AllocateChannels( 32 ) ); m_quiet = false; /* int frequency, channels; Uint16 format; channels = frequency = format = 0; if( Mix_QuerySpec( &frequency, &format, &channels) == 1 ) { LOG( 1 ) INFO( "Audio::Audio: Mixer opened with %i Hz and %i channels\n", frequency, channels ); } else { LOG( 1 ) INFO( "Audio::Audio: Couldn't open mixer\n" ); } */ // insert menu track as entry 0 const string menuTrackName( Loader::getInstance()->getAbsolutePath( "sound/effects/intro.ogg" )); m_track.push_back( TrackDesc( menuTrackName )); m_track[0].playMe = false; // read music dir String musicDir; if( settings->getMusicDir( musicDir ) ) { string dir( musicDir.getString() ); addTracksInDir( 5, dir ); } } /******************************************************************************/ Audio::~Audio() { QUIET_RETURN; LOG( 1 ) INFO( "Audio::~Audio: starting destructor\n"); dumpToFile( NULL ); stopTrack(); Mix_HaltChannel( -1 ); Mix_CloseAudio(); SDL_QuitSubSystem( SDL_INIT_AUDIO ); LOG( 1 ) INFO( "Audio::~Audio: done\n" ); } /******************************************************************************/ void Audio::update() { QUIET_RETURN; if( m_playNextTrack ) { m_playNextTrack = false; if( loadTrack() ) playTrack(); else m_playNextTrack = true; } } /******************************************************************************/ Mix_Chunk* Audio::loadSound( const char* filename ) const { QUIET_RETURN NULL; Mix_Chunk* chunk = 0; CHECK( ( chunk = Loader::getInstance()->getSound(filename, Loader::noErrorAbort) ), "Audio::loadSound: couldn't load sound in file %s\n", filename ); return chunk; } /******************************************************************************/ void Audio::playSound( Mix_Chunk* chunk ) { QUIET_RETURN; if( ! chunk ) return; // NULL samples shouldn't be played const int channel = Mix_PlayChannel( -1, chunk, 0 ); DBG( 5 ) if( ! CHECK( channel != -1, "Audio::playSound: couldn't play sound\n" ) ) return; if ( m_isRecording ) { if ( !m_currentRecordFrame ) m_currentRecordFrame = new std::vector; m_currentRecordFrame->push_back( chunk ); } } /******************************************************************************/ void Audio::playSound( Mix_Chunk* chunk, const int sourcePositionX, const int sourcePositionY ) { QUIET_RETURN; if( ! chunk ) return; // NULL samples shouldn't be played const real DECAY = 0.5; // higher value -> faster decay with distance const int MIN_VOLUME = 220; // the lowest volume for distant sound sources; has to be <= 255 const int channel = Mix_PlayChannel( -1, chunk, 0 ); DBG( 5 ) if( ! CHECK( channel != -1, "Audio::playSound: couldn't play sound\n" ) ) return; if ( m_isRecording ) { if ( !m_currentRecordFrame ) m_currentRecordFrame = new std::vector; m_currentRecordFrame->push_back( chunk ); } const int dx = sourcePositionX - m_listeningPositionX, dy = sourcePositionY - m_listeningPositionY; const int dist = ROUND( DECAY * SQRT_REAL( static_cast( dx*dx + dy*dy ) ) ); Sint16 angle; if( dist <= 5 ) angle = 0; else angle = ROUND( ATAN2_REAL( static_cast( dy ), static_cast( dx )) * 180 / M_PI ) + 90; Mix_SetPosition( channel, angle, static_cast( dist > MIN_VOLUME ? MIN_VOLUME : dist ) ); } /******************************************************************************/ static void fileDumpProcessor( void* udata, Uint8* stream, int len ) { if( fwrite( stream, 1, len, reinterpret_cast( udata )) != static_cast( len )) { CHECK( false, "fileDumpProcessor (in Audio): could not write audio data\n" ); Audio::getInstance()->dumpToFile( NULL ); } } /******************************************************************************/ bool Audio::dumpToFile( const char* filename ) { if( filename ) { // open new file if( m_outputFile ) dumpToFile( NULL ); // if already open, close it before m_outputFile = fopen( filename, "w" ); if( ! CHECK( m_outputFile, "Audio::dumpToFile: could not open file '%s'\n", filename )) return false; Mix_SetPostMix( fileDumpProcessor, m_outputFile ); // register processor } else { // close file if( m_outputFile ) { // only if open Mix_SetPostMix( NULL, NULL ); fclose( m_outputFile ); m_outputFile = NULL; } } return true; } /******************************************************************************/ void Audio::startRecording() { // clear record vector for ( std::vector*>::iterator iter = m_recordVector.begin(); iter != m_recordVector.end(); iter++ ) { delete *iter; } m_recordVector.clear(); m_currentRecordFrame = 0; m_isRecording = true; } void Audio::stopRecording() { m_isRecording = false; } void Audio::nextRecordFrame() { m_recordVector.push_back( m_currentRecordFrame ); m_currentRecordFrame = 0; } void Audio::writeRecordToFile( const char* filename ) { stopRecording(); dumpToFile( filename ); for ( std::vector*>::iterator iter = m_recordVector.begin(); iter != m_recordVector.end(); iter++ ) { std::vector* currentFrame = *iter; if ( currentFrame ) { for ( std::vector::iterator frameIter = currentFrame->begin(); frameIter != currentFrame->end(); frameIter++ ) { Mix_Chunk* chunk = *frameIter; if ( chunk ) Mix_PlayChannel( -1, chunk, 0 ); } } SDL_Delay( 40 ); } dumpToFile( 0 ); } /******************************************************************************/ void Audio::addTracksInDir( const int depth, const string& dir ) { struct stat fileStat; LOG( 3 ) INFO( "Audio::Audio: looking for suitable sound files in '%s'\n", dir.c_str() ); DIR* musicDir = opendir( dir.c_str() ); if( CHECK( musicDir, "Audio::Audio: could not open music directory '%s'\n", dir.c_str() )) { // look through directory files while( true ) { dirent* dirEntry = readdir( musicDir ); if( ! dirEntry ) break; // no more files in directory const string entryName = dir + '/' + string( dirEntry->d_name ); LOG( 5 ) INFO( "Audio::Audio: found file '%s'\n", entryName.c_str() ); // try to stat file if( stat( entryName.c_str(), &fileStat ) != 0 ) continue; // is it a file? if( S_ISREG( fileStat.st_mode )) { // it is a file, so go on LOG( 5 ) INFO( "Audio::Audio: '%s' is file or link\n", entryName.c_str() ); // test suffix if( entryName.compare( entryName.size()-4, 4, string( ".mp3" )) == 0 || entryName.compare( entryName.size()-4, 4, string( ".ogg" )) == 0 || entryName.compare( entryName.size()-4, 4, string( ".mid" )) == 0 || entryName.compare( entryName.size()-4, 4, string( ".mod" )) == 0 || entryName.compare( entryName.size()-4, 4, string( ".wav" )) == 0 ) { // everything is ok, append to track files LOG( 4 ) INFO( "Audio::Audio: adding '%s' to track files\n", entryName.c_str() ); m_track.push_back( TrackDesc( entryName )); } } else // are we not too deep and is it a directory and is it not a hidden dir? if( depth > 0 && S_ISDIR( fileStat.st_mode ) && dirEntry->d_name[0] != '.' ) { addTracksInDir( depth-1, entryName ); } } closedir( musicDir ); } } /******************************************************************************/ void Audio::setJukeboxMode( const bool inJukeboxMode ) { QUIET_RETURN; if( m_track.size() <= 1 ) return; if( m_inJukeboxMode ) { Mix_HookMusicFinished( 0 ); stopTrack(); } m_inJukeboxMode = inJukeboxMode; if( m_inJukeboxMode ) { Mix_HookMusicFinished( trackEnded ); m_playNextTrack = ! (Mix_PlayingMusic() || Mix_FadingMusic() != MIX_NO_FADING); } else m_playNextTrack = false; } /******************************************************************************/ bool Audio::loadTrack( int track ) { QUIET_RETURN false; // track index out of bounds? if( ! CHECK( track < static_cast( m_track.size() ), "Audio::loadTrack: " "track index too big\n" )) return false; if( m_currentTrack ) { stopTrack(); Mix_FreeMusic( m_currentTrack ); m_currentTrack = 0; } if( track < 0 ) { track = localRnd.getUint32Between( 1, m_track.size()-1 ); } LOG( 2 ) INFO( "Audio::loadTrack: trying to load track %i (\"%s\")\n", track, m_track[track].trackName.c_str() ); m_currentTrack = Mix_LoadMUS( m_track[track].trackName.c_str() ); LOG( 5 ) INFO( "Audio::loadTrack: track started\n" ); return CHECK( m_currentTrack != 0, "Audio::loadTrack: could not load track %i " "(\"%s\"); SDL error: %s\n", track, m_track[track].trackName.c_str(), Mix_GetError() ); } /******************************************************************************/ void Audio::playTrack() { fadeInTrack( 0 ); } /******************************************************************************/ void Audio::stopTrack() { fadeOutTrack( 0 ); } /******************************************************************************/ void Audio::fadeInTrack( const int time ) { LOG( 5 ) INFO( "Audio::fadeInTrack: fading in\n" ); if( m_currentTrack ) { if( time > 0 ) Mix_FadeInMusic( m_currentTrack, 1, time ); else Mix_PlayMusic( m_currentTrack, 1 ); } LOG( 5 ) INFO( "Audio::fadeInTrack: fading in done\n" ); } /******************************************************************************/ void Audio::fadeOutTrack( const int time ) { LOG( 5 ) INFO( "Audio::fadeOutTrack: fading out\n" ); if( m_currentTrack ) { if( time > 0 ) Mix_FadeOutMusic( time ); else Mix_HaltMusic(); } LOG( 5 ) INFO( "Audio::fadeOutTrack: fading out done\n" ); } /******************************************************************************/ void Audio::setLowPassFilter ( const double val ) const { //Mix_SetPostMix( lowPass, NULL ); if( ! m_lowPassFilterEnabled && val > 0.0 ) { m_lowPassFilterEnabled = true; Mix_RegisterEffect( MIX_CHANNEL_POST, lowPassFilter, NULL , NULL ); } if( m_lowPassFilterEnabled && val <= 0.0 ) { Mix_UnregisterEffect( MIX_CHANNEL_POST, lowPassFilter ); m_lowPassFilterEnabled = false; } lowPassFilterVal = max( 0.0, val ); } /******************************************************************************/