// soundmgr.cxx -- Sound effect management class // // Sound manager initially written by David Findlay // 2001 // // C++-ified by Curtis Olson, started March 2001. // // Copyright (C) 2001 Curtis L. Olson - curt@flightgear.org // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // // $Id$ #include #include #include
#include "soundmgr.hxx" #define FG_SOUND_SAFETY_MULT 3 #define FG_MAX_SOUND_SAFETY ( 1.0 / FG_SOUND_SAFETY_MULT ) // constructor FGSimpleSound::FGSimpleSound( string file ) { SGPath slfile( globals->get_fg_root() ); slfile.append( file ); sample = new slSample ( (char *)slfile.c_str() ); pitch_envelope = new slEnvelope( 1, SL_SAMPLE_ONE_SHOT ); volume_envelope = new slEnvelope( 1, SL_SAMPLE_ONE_SHOT ); pitch_envelope->setStep ( 0, 0.01, 1.0 ); volume_envelope->setStep ( 0, 0.01, 1.0 ); } FGSimpleSound::FGSimpleSound( unsigned char *buffer, int len ) { sample = new slSample ( buffer, len ); pitch_envelope = new slEnvelope( 1, SL_SAMPLE_ONE_SHOT ); volume_envelope = new slEnvelope( 1, SL_SAMPLE_ONE_SHOT ); pitch_envelope->setStep ( 0, 0.01, 1.0 ); volume_envelope->setStep ( 0, 0.01, 1.0 ); } // destructor FGSimpleSound::~FGSimpleSound() { delete pitch_envelope; delete volume_envelope; delete sample; } // constructor FGSoundMgr::FGSoundMgr() { audio_sched = new slScheduler( 8000 ); audio_sched -> setMaxConcurrent ( 6 ); audio_mixer = new smMixer; SG_LOG( SG_GENERAL, SG_INFO, "Rate = " << audio_sched->getRate() << " Bps = " << audio_sched->getBps() << " Stereo = " << audio_sched->getStereo() ); } // destructor FGSoundMgr::~FGSoundMgr() { sound_map_iterator current = sounds.begin(); sound_map_iterator end = sounds.end(); for ( ; current != end; ++current ) { FGSimpleSound *s = current->second; delete s->get_sample(); delete s; } delete audio_sched; delete audio_mixer; } // initialize the sound manager bool FGSoundMgr::init() { last.stamp(); safety = FG_MAX_SOUND_SAFETY; // audio_mixer -> setMasterVolume ( 80 ) ; /* 80% of max volume. */ audio_sched -> setSafetyMargin ( FG_SOUND_SAFETY_MULT * safety ) ; sound_map_iterator current = sounds.begin(); sound_map_iterator end = sounds.end(); for ( ; current != end; ++current ) { FGSimpleSound *s = current->second; delete s->get_sample(); delete s; } sounds.clear(); if ( audio_sched->not_working() ) { return false; } else { return true; } } // run the audio scheduler bool FGSoundMgr::update() { SGTimeStamp current; current.stamp(); double elapsed = (double)(current - last) / 1000000.0; last = current; if ( elapsed > safety ) { safety = elapsed; } else { safety = safety * 0.99 + elapsed * 0.01; } if ( safety > FG_MAX_SOUND_SAFETY ) { safety = FG_MAX_SOUND_SAFETY; } // cout << "safety = " << safety << endl; audio_sched -> setSafetyMargin ( FG_SOUND_SAFETY_MULT * safety ) ; if ( !audio_sched->not_working() ) { audio_sched -> update(); return true; } else { return false; } } // add a sound effect, return true if successful bool FGSoundMgr::add( FGSimpleSound *sound, const string& refname ) { sounds[refname] = sound; return true; } // remove a sound effect, return true if successful bool FGSoundMgr::remove( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { // first stop the sound from playing (so we don't bomb the // audio scheduler) FGSimpleSound *sample = it->second; // cout << "Playing " << sample->get_sample()->getPlayCount() // << " instances!" << endl; audio_sched->stopSample( sample->get_sample() ); audio_sched->addSampleEnvelope( sample->get_sample(), 0, 0, NULL, SL_PITCH_ENVELOPE ); audio_sched->addSampleEnvelope( sample->get_sample(), 0, 1, NULL, SL_VOLUME_ENVELOPE ); #if defined ( PLIB_1_2_X ) // if PLIB_1_2_X, we can't reliably remove sounds // that are currently being played. :-( So, let's just not // remove them and return false. The effects of this are that // the sound sample will continue to finish playing (or // continue to loop forever.) And the sound sample will // remain registered in the plib audio system. This is a // memory leak, and eventually this could cause us to max out // the total number of allowed sound samples in plib, but what // are you going to do? Hopefully the plib team will do a new // stable relase with these problems fixed. // cout << "plib broken audio, skipping actual remove" << endl; return false; #else // must call audio_sched->update() after stopping the sound // but before deleting it. audio_sched -> update(); // cout << "Still playing " << sample->get_sample()->getPlayCount() // << " instances!" << endl; delete sample; sounds.erase( it ); return true; #endif } else { return false; } } // return true of the specified sound exists in the sound manager system bool FGSoundMgr::exists( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { return true; } else { return false; } } // return a pointer to the FGSimpleSound if the specified sound exists // in the sound manager system, otherwise return NULL FGSimpleSound *FGSoundMgr::find( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { return it->second; } else { return NULL; } } // tell the scheduler to play the indexed sample in a continuous // loop bool FGSoundMgr::play_looped( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { FGSimpleSound *sample = it->second; audio_sched->loopSample( sample->get_sample() ); audio_sched->addSampleEnvelope( sample->get_sample(), 0, 0, sample->get_pitch_envelope(), SL_PITCH_ENVELOPE ); audio_sched->addSampleEnvelope( sample->get_sample(), 0, 1, sample->get_volume_envelope(), SL_VOLUME_ENVELOPE ); return true; } else { return false; } } // tell the scheduler to play the indexed sample once bool FGSoundMgr::play_once( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { FGSimpleSound *sample = it->second; audio_sched->stopSample( sample->get_sample() ); audio_sched->playSample( sample->get_sample() ); audio_sched->addSampleEnvelope( sample->get_sample(), 0, 0, sample->get_pitch_envelope(), SL_PITCH_ENVELOPE ); audio_sched->addSampleEnvelope( sample->get_sample(), 0, 1, sample->get_volume_envelope(), SL_VOLUME_ENVELOPE ); return true; } else { return false; } } // return true of the specified sound is currently being played bool FGSoundMgr::is_playing( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { FGSimpleSound *sample = it->second; return (sample->get_sample()->getPlayCount() > 0 ); return true; } else { return false; } } // immediate stop playing the sound bool FGSoundMgr::stop( const string& refname ) { sound_map_iterator it = sounds.find( refname ); if ( it != sounds.end() ) { FGSimpleSound *sample = it->second; audio_sched->stopSample( sample->get_sample() ); return true; } else { return false; } }