5a849b66e8
- changed FGSubsystem::update(int) to FGSubsystem::update(delta_time_sec); the argument is now delta time in seconds rather than milliseconds - added FGSubsystem::suspend(), FGSubsystem::suspend(bool), FGSubsystem::resume(), and FGSubsystem::is_suspended(), all with default implementations; is_suspended takes account of the master freeze as well as the subsystem's individual suspended state - the FDMs now use the delta time argument the same as the rest of FlightGear; formerly, main.cxx made a special case and passed a multiloop argument - FDMs now calculate multiloop internally instead of relying on main.cxx There are probably some problems -- I've done basic testing with the major FDMs and subsystems, but we'll probably need a few weeks to sniff out bugs.
403 lines
9.8 KiB
C++
403 lines
9.8 KiB
C++
// soundmgr.cxx -- Sound effect management class
|
|
//
|
|
// Sound manager initially written by David Findlay
|
|
// <david_j_findlay@yahoo.com.au> 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 <simgear/debug/logstream.hxx>
|
|
#include <simgear/misc/sg_path.hxx>
|
|
|
|
#include <Main/globals.hxx>
|
|
|
|
#include "soundmgr.hxx"
|
|
|
|
#define FG_SOUND_SAFETY_MULT 3
|
|
#define FG_MAX_SOUND_SAFETY ( 1.0 / FG_SOUND_SAFETY_MULT )
|
|
|
|
//
|
|
// SimpleSound
|
|
//
|
|
|
|
// constructor
|
|
FGSimpleSound::FGSimpleSound( string file )
|
|
: pitch(1.0),
|
|
volume(1.0)
|
|
{
|
|
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 )
|
|
: pitch(1.0),
|
|
volume(1.0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void FGSimpleSound::play( slScheduler *sched, bool looped ) {
|
|
|
|
// make sure sound isn't already playing
|
|
if ( sample->getPlayCount() > 0 ) {
|
|
sched->stopSample(sample);
|
|
// return;
|
|
}
|
|
|
|
if ( looped ) {
|
|
sched->loopSample(sample);
|
|
} else {
|
|
sched->playSample(sample);
|
|
}
|
|
|
|
sched->addSampleEnvelope(sample, 0, 0, pitch_envelope, SL_PITCH_ENVELOPE);
|
|
sched->addSampleEnvelope(sample, 0, 1, volume_envelope, SL_VOLUME_ENVELOPE);
|
|
}
|
|
|
|
void FGSimpleSound::stop( slScheduler *sched, bool quick ) {
|
|
|
|
sched->stopSample( sample );
|
|
}
|
|
|
|
//
|
|
// Sound Manager
|
|
//
|
|
|
|
// constructor
|
|
FGSoundMgr::FGSoundMgr() {
|
|
audio_sched = new slScheduler( 8000 );
|
|
if ( audio_sched->notWorking() ) {
|
|
SG_LOG( SG_GENERAL, SG_ALERT, "Audio initialization failed!" );
|
|
} else {
|
|
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() {
|
|
|
|
//
|
|
// Remove the samples from the sample manager.
|
|
//
|
|
sample_map_iterator sample_current = samples.begin();
|
|
sample_map_iterator sample_end = samples.end();
|
|
for ( ; sample_current != sample_end; ++sample_current ) {
|
|
sample_ref *sr = sample_current->second;
|
|
delete sr->sample;
|
|
delete sr;
|
|
}
|
|
|
|
//
|
|
// Remove the sounds from the sound manager.
|
|
//
|
|
sound_map_iterator sound_current = sounds.begin();
|
|
sound_map_iterator sound_end = sounds.end();
|
|
for ( ; sound_current != sound_end; ++sound_current ) {
|
|
FGSimpleSound *s = sound_current->second;
|
|
delete s;
|
|
}
|
|
|
|
delete audio_sched;
|
|
delete audio_mixer;
|
|
}
|
|
|
|
|
|
// initialize the sound manager
|
|
void 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 ) ;
|
|
|
|
|
|
//
|
|
// Remove the samples from the sample manager.
|
|
//
|
|
sample_map_iterator sample_current = samples.begin();
|
|
sample_map_iterator sample_end = samples.end();
|
|
for ( ; sample_current != sample_end; ++sample_current ) {
|
|
sample_ref *sr = sample_current->second;
|
|
delete sr->sample;
|
|
delete sr;
|
|
}
|
|
samples.clear();
|
|
|
|
//
|
|
// Remove the sounds from the sound manager.
|
|
//
|
|
sound_map_iterator sound_current = sounds.begin();
|
|
sound_map_iterator sound_end = sounds.end();
|
|
for ( ; sound_current != sound_end; ++sound_current ) {
|
|
FGSimpleSound *s = sound_current->second;
|
|
delete s->get_sample();
|
|
delete s;
|
|
}
|
|
sounds.clear();
|
|
|
|
}
|
|
|
|
void FGSoundMgr::bind ()
|
|
{
|
|
// no properties yet
|
|
}
|
|
|
|
void FGSoundMgr::unbind ()
|
|
{
|
|
// no properties yet
|
|
}
|
|
|
|
|
|
// run the audio scheduler
|
|
void FGSoundMgr::update(double dt) {
|
|
// FIXME: use dt supplied (seconds)
|
|
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();
|
|
}
|
|
|
|
|
|
void
|
|
FGSoundMgr::pause ()
|
|
{
|
|
audio_sched->pauseSample(0, 0);
|
|
}
|
|
|
|
|
|
void
|
|
FGSoundMgr::resume ()
|
|
{
|
|
audio_sched->resumeSample(0, 0);
|
|
}
|
|
|
|
|
|
// add a sound effect, return true if successful
|
|
bool FGSoundMgr::add( FGSimpleSound *sound, const string& refname ) {
|
|
|
|
sound_map_iterator sound_it = sounds.find( refname );
|
|
if ( sound_it != sounds.end() ) {
|
|
// sound already exists
|
|
return false;
|
|
}
|
|
|
|
sample_map_iterator sample_it = samples.find( refname );
|
|
if ( sample_it != samples.end() ) {
|
|
// this sound has existed in the past and it's sample is still
|
|
// here, delete the sample so we can replace it.
|
|
samples.erase( sample_it );
|
|
}
|
|
|
|
sample_ref *sr = new sample_ref;
|
|
|
|
sr->n=1;
|
|
sr->sample = sound->get_sample();
|
|
samples[refname] = sr;
|
|
|
|
sounds[refname] = sound;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// add a sound from a file, return the sample if successful, else return NULL
|
|
FGSimpleSound *FGSoundMgr::add( const string& refname, const string &file ) {
|
|
FGSimpleSound *sound;
|
|
|
|
if (file.empty())
|
|
return NULL;
|
|
|
|
sample_map_iterator it = samples.find(file);
|
|
if (it == samples.end()) {
|
|
sound = new FGSimpleSound(file);
|
|
sounds[refname] = sound;
|
|
|
|
sample_ref *sr = new sample_ref;
|
|
|
|
sr->n=1;
|
|
sr->sample = sound->get_sample();
|
|
samples[file] = sr;
|
|
|
|
} else {
|
|
sample_ref *sr = it->second;
|
|
|
|
sr->n++;
|
|
sound =
|
|
new FGSimpleSound(sr->sample->getBuffer(), sr->sample->getLength());
|
|
sounds[refname] = sound;
|
|
|
|
}
|
|
|
|
return sound;
|
|
}
|
|
|
|
|
|
// 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 );
|
|
|
|
// 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;
|
|
|
|
//
|
|
// FIXME:
|
|
// Due to the change in the sound manager, samples live
|
|
// until the sound manager gets removed.
|
|
//
|
|
// delete sample;
|
|
sounds.erase( it );
|
|
|
|
// cout << "sndmgr: removed -> " << refname << endl;
|
|
return true;
|
|
} else {
|
|
// cout << "sndmgr: failed remove -> " << refname << endl;
|
|
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 ) {
|
|
FGSimpleSound *sample;
|
|
|
|
if ((sample = find( refname )) == NULL)
|
|
return false;
|
|
|
|
sample->play(audio_sched, true);
|
|
return true;
|
|
}
|
|
|
|
|
|
// tell the scheduler to play the indexed sample once
|
|
bool FGSoundMgr::play_once( const string& refname ) {
|
|
FGSimpleSound *sample;
|
|
|
|
if ((sample = find( refname )) == NULL)
|
|
return false;
|
|
|
|
sample->play(audio_sched, false);
|
|
return true;
|
|
}
|
|
|
|
|
|
// return true of the specified sound is currently being played
|
|
bool FGSoundMgr::is_playing( const string& refname ) {
|
|
FGSimpleSound *sample;
|
|
|
|
if ((sample = find( refname )) == NULL)
|
|
return false;
|
|
|
|
return (sample->get_sample()->getPlayCount() > 0 );
|
|
}
|
|
|
|
|
|
// immediate stop playing the sound
|
|
bool FGSoundMgr::stop( const string& refname ) {
|
|
FGSimpleSound *sample;
|
|
|
|
if ((sample = find( refname )) == NULL)
|
|
return false;
|
|
|
|
audio_sched->stopSample( sample->get_sample() );
|
|
return true;
|
|
}
|