diff --git a/simgear/compiler.h b/simgear/compiler.h index da0020cc..eca95fe5 100644 --- a/simgear/compiler.h +++ b/simgear/compiler.h @@ -131,9 +131,35 @@ // #ifdef __APPLE__ +# ifdef __GNUC__ +# if ( __GNUC__ > 3 ) || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 3 ) inline int (isnan)(double r) { return !(r <= 0 || r >= 0); } -#else +# else + // any C++ header file undefines isinf and isnan + // so this should be included before + // the functions are STILL in libm (libSystem on mac os x) +extern "C" int (isnan)(double); +extern "C" int (isinf)(double); +# endif +# else +inline int (isnan)(double r) { return !(r <= 0 || r >= 0); } +# endif +#endif +#if defined (__FreeBSD__) +# if __FreeBSD_version < 500000 + extern "C" { + inline int isnan(double r) { return !(r <= 0 || r >= 0); } + } +# endif +#endif + +#if defined (__CYGWIN__) +# include // isnan +#endif + +#if defined(__MINGW32__) +# define isnan(x) _isnan(x) #endif diff --git a/simgear/environment/visual_enviro.cxx b/simgear/environment/visual_enviro.cxx index 42b61fd0..b0cb2190 100644 --- a/simgear/environment/visual_enviro.cxx +++ b/simgear/environment/visual_enviro.cxx @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -175,7 +175,7 @@ SGEnviro::SGEnviro() : lightning_enable_state(false), elapsed_time(0.0), dt(0.0), - soundMgr(NULL), + sampleGroup(NULL), snd_active(false), snd_dist(0.0), min_time_before_lt(0.0), @@ -189,6 +189,8 @@ SGEnviro::SGEnviro() : } SGEnviro::~SGEnviro(void) { + if (sampleGroup) delete sampleGroup; + // OSGFIXME return; list_of_lightning::iterator iLightning; @@ -530,8 +532,8 @@ void SGEnviro::drawRain(double pitch, double roll, double heading, double hspeed } -void SGEnviro::set_soundMgr(SGSoundMgr *mgr) { - soundMgr = mgr; +void SGEnviro::set_sampleGroup(SGSampleGroup *sgr) { + sampleGroup = sgr; } void SGEnviro::drawPrecipitation(double rain_norm, double snow_norm, double hail_norm, double pitch, double roll, double heading, double hspeed) { @@ -616,7 +618,7 @@ void SGLightning::lt_build(void) { top[PY] = alt; top[PZ] = 0; lt_build_tree_branch(0, top, 1.0, 50, top[PY] / 8.0); - if( ! sgEnviro.soundMgr ) + if( ! sgEnviro.sampleGroup ) return; Point3D start( sgEnviro.last_lon*SG_DEGREES_TO_RADIANS, sgEnviro.last_lat*SG_DEGREES_TO_RADIANS, 0.0 ); Point3D dest( lon*SG_DEGREES_TO_RADIANS, lat*SG_DEGREES_TO_RADIANS, 0.0 ); @@ -751,15 +753,15 @@ void SGEnviro::drawLightning(void) { double ax = 0.0, ay = 0.0; ax = cos(course) * dist; ay = sin(course) * dist; - SGSharedPtr snd = soundMgr->find("thunder"); + SGSharedPtr snd = sampleGroup->find("thunder"); if( snd ) { - ALfloat pos[3]={ax, ay, -sgEnviro.last_alt }; - snd->set_source_pos(pos); + SGVec3d pos = SGVec3d(ax, ay, -sgEnviro.last_alt); + snd->set_position(pos); snd->play_once(); } } } else { - if( !soundMgr->is_playing("thunder") ) { + if( !sampleGroup->is_playing("thunder") ) { snd_active = false; snd_playing = false; } diff --git a/simgear/environment/visual_enviro.hxx b/simgear/environment/visual_enviro.hxx index 51a76a06..08830c0b 100644 --- a/simgear/environment/visual_enviro.hxx +++ b/simgear/environment/visual_enviro.hxx @@ -32,7 +32,7 @@ using std::vector; using std::string; class SGLightning; -class SGSoundMgr; +class SGSampleGroup; /** * Simulate some echo on a weather radar. @@ -84,7 +84,7 @@ private: sgVec4 fog_color; sgMat4 transform; double last_lon, last_lat, last_alt; - SGSoundMgr *soundMgr; + SGSampleGroup *sampleGroup; bool snd_active, snd_playing; double snd_timer, snd_wait, snd_pos_lat, snd_pos_lon, snd_dist; double min_time_before_lt; @@ -243,7 +243,7 @@ public: * Forward the sound manager instance to be able to play samples. * @param mgr a running sound manager */ - void set_soundMgr(SGSoundMgr *mgr); + void set_sampleGroup(SGSampleGroup *sgr); void setFOV( float w, float h ); void getFOV( float &w, float &h ); diff --git a/simgear/sound/Makefile.am b/simgear/sound/Makefile.am index e421cdef..d14c9fb7 100644 --- a/simgear/sound/Makefile.am +++ b/simgear/sound/Makefile.am @@ -7,30 +7,43 @@ lib_LIBRARIES = libsgsound.a noinst_HEADERS = include_HEADERS = \ + sample_group.hxx \ sample_openal.hxx \ soundmgr_openal.hxx \ xmlsound.hxx libsgsound_a_SOURCES = \ + sample_group.cxx \ sample_openal.cxx \ soundmgr_openal.cxx \ xmlsound.cxx -#noinst_PROGRAMS = openal_test1 openal_test2 +check_PROGRAMS = openal_test1 openal_test2 openal_test3 -#openal_test1_SOURCES = openal_test1.cxx -#openal_test2_SOURCES = openal_test2.cxx +openal_test1_SOURCES = openal_test1.cxx +openal_test2_SOURCES = openal_test2.cxx +openal_test3_SOURCES = openal_test3.cxx -#openal_test1_LDADD = \ -# $(top_builddir)/simgear/debug/libsgdebug.a \ -# $(openal_LIBS) +openal_test1_LDADD = \ + $(top_builddir)/simgear/debug/libsgdebug.a \ + $(openal_LIBS) -#openal_test2_LDADD = \ -# libsgsound.a \ -# $(top_builddir)/simgear/debug/libsgdebug.a \ -# $(top_builddir)/simgear/misc/libsgmisc.a \ -# $(top_builddir)/simgear/structure/libsgstructure.a \ -# $(openal_LIBS) \ -# -lOpenThreads +openal_test2_LDADD = \ + libsgsound.a \ + $(top_builddir)/simgear/structure/libsgstructure.a \ + $(top_builddir)/simgear/timing/libsgtiming.a \ + $(top_builddir)/simgear/debug/libsgdebug.a \ + $(top_builddir)/simgear/misc/libsgmisc.a \ + $(top_builddir)/simgear/math/libsgmath.a \ + $(openal_LIBS) + +openal_test3_LDADD = \ + libsgsound.a \ + $(top_builddir)/simgear/structure/libsgstructure.a \ + $(top_builddir)/simgear/timing/libsgtiming.a \ + $(top_builddir)/simgear/debug/libsgdebug.a \ + $(top_builddir)/simgear/misc/libsgmisc.a \ + $(top_builddir)/simgear/math/libsgmath.a \ + $(openal_LIBS) -lstdc++ INCLUDES = -I$(top_srcdir) -DSRC_DIR=\"$(top_srcdir)/simgear/sound\" diff --git a/simgear/sound/README b/simgear/sound/README new file mode 100644 index 00000000..8f9546b5 --- /dev/null +++ b/simgear/sound/README @@ -0,0 +1,40 @@ + +All code in this directory uses the OpenAL coordinate system for maximum +useablity. The OpenAL coordinate system is equal to that of OpenGL with the +main difference that objects behind the viewer can still be heard: + - positive x is to the right + - positive y is upwards + - positive z is towards the back of the viewer/listener + +see: http://www.falloutsoftware.com/tutorials/gl/cartesian.gif + +All positions are in cartesian space with a unit length of one meter. + +Velocities are three tuples indicating speed and direction in the same space +as positions do (so they are not in the models local space). + + +There is one SoundMgr that handles multiple SoundGroup classes. +Each SoundGroup class handles multiple SoundSample classes. + +A SoundSample class defines the properties of each individual sound like +pitch, volume, position, orientation, sound cone paramters, etc. This +class can be created all over the code but *has* to be assigned to a +SampelGroup before you can hear it. Current sample groups are "atc", +"avionics" and "fx" for the master airplane effects. The position of a +SoundSample is relative to (0,0,0) of the model and hence relative to +the base position of the SampleGroup. + +A SampleGroup class has to be assigned to the SoundManager to be heard +and holds data of a group of samples (maybe 'sample cloud' might be a +good description). This class has to be created for each individual +model that can produce one or more sounds. The SampleGroup class can be +altered by modifying it's volume, position and orientation. +Repositioning this class also means repositioning all it's associated +samples. Altering it's orientation also means repositioning the absolute +(real world) position (and orientation) of the associated sound samples. + +The SoundMaganer can be repositioned which basically means moving the +listener around. It's also possible to alter the listener orientation en +velocity with this class, together with the master volume. + diff --git a/simgear/sound/jet.wav b/simgear/sound/jet.wav index 891c84d0..a06f2afe 100644 Binary files a/simgear/sound/jet.wav and b/simgear/sound/jet.wav differ diff --git a/simgear/sound/openal_test1.cxx b/simgear/sound/openal_test1.cxx index 5342ddf1..66678f8b 100644 --- a/simgear/sound/openal_test1.cxx +++ b/simgear/sound/openal_test1.cxx @@ -14,6 +14,10 @@ static unsigned int sleep(unsigned int secs) { return 0; } # include # include # include +#elif defined(OPENALSDK) +# include +# include +# include #else # include # include @@ -46,6 +50,9 @@ int main( int argc, char *argv[] ) { ALCdevice *dev; ALCcontext *context; + alutInit(&argc, argv); + sglog().setLogLevels( SG_ALL, SG_ALERT ); + // initialize OpenAL if ( (dev = alcOpenDevice( NULL )) != NULL && ( context = alcCreateContext( dev, NULL )) != NULL ) { @@ -158,6 +165,7 @@ int main( int argc, char *argv[] ) { alSourcePlay( source ); sleep(10); + alutExit(); return 0; } diff --git a/simgear/sound/openal_test2.cxx b/simgear/sound/openal_test2.cxx index da263620..eb0da57e 100644 --- a/simgear/sound/openal_test2.cxx +++ b/simgear/sound/openal_test2.cxx @@ -1,54 +1,102 @@ #include -#ifdef __MINGW32__ -// This is broken, but allows the file to compile without a POSIX -// environment. -static unsigned int sleep(unsigned int secs) { return 0; } +#ifdef _WIN32 +#include +#define sleep(x) Sleep(x*1000) #else -#include // sleep() +#include #endif -#include "sample_openal.hxx" +#include +#include + #include "soundmgr_openal.hxx" +SGGeod pos = SGGeod::fromDeg(0,0); int main( int argc, char *argv[] ) { - SGSoundMgr sm; + SGSampleGroup *sgr; + SGSoundMgr *smgr; - SGSoundSample sample1( SRC_DIR, "jet.wav" ); - sample1.set_volume(0.5); - sample1.set_volume(0.2); - sample1.play_looped(); + smgr = new SGSoundMgr; + + smgr->bind(); + smgr->init(); + sgr = smgr->find("default", true); + smgr->set_volume(0.9); + smgr->set_position( SGVec3d::fromGeod(pos), pos ); + smgr->activate(); + + SGSoundSample *sample1 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample1->set_volume(1.0); + sample1->set_pitch(1.0); + sample1->play_looped(); + sgr->add(sample1, "sound1"); + smgr->update(1.0); + printf("playing sample1\n"); sleep(1); - SGSoundSample sample2( SRC_DIR, "jet.wav" ); - sample2.set_volume(0.5); - sample2.set_pitch(0.4); - sample2.play_looped(); + SGSoundSample *sample2 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample2->set_volume(0.5); + sample2->set_pitch(0.4); + sample2->play_looped(); + sgr->add(sample2, "sound2"); + smgr->update(1.0); + printf("playing sample2\n"); sleep(1); - SGSoundSample sample3( SRC_DIR, "jet.wav" ); - sample3.set_volume(0.5); - sample3.set_pitch(0.8); - sample3.play_looped(); + SGSoundSample *sample3 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample3->set_volume(0.5); + sample3->set_pitch(0.8); + sample3->play_looped(); + sgr->add(sample3, "sound3"); + smgr->update(1.0); + printf("playing sample3\n"); sleep(1); - SGSoundSample sample4( SRC_DIR, "jet.wav" ); - sample4.set_volume(0.5); - sample4.set_pitch(1.2); - sample4.play_looped(); + SGSoundSample *sample4 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample4->set_volume(0.5); + sample4->set_pitch(1.2); + sample4->play_looped(); + sgr->add(sample4, "sound4"); + smgr->update(1.0); + printf("playing sample4\n"); sleep(1); - SGSoundSample sample5( SRC_DIR, "jet.wav" ); - sample5.set_volume(0.5); - sample5.set_pitch(1.6); - sample5.play_looped(); + SGSoundSample *sample5 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample5->set_volume(0.5); + sample5->set_pitch(1.6); + sample5->play_looped(); + sgr->add(sample5, "sound5"); + smgr->update(1.0); + printf("playing sample5\n"); sleep(1); - SGSoundSample sample6( SRC_DIR, "jet.wav" ); - sample6.set_volume(0.5); - sample6.set_pitch(2.0); - sample6.play_looped(); + SGSoundSample *sample6 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample6->set_volume(0.5); + sample6->set_pitch(2.0); + sample6->play_looped(); + sgr->add(sample6, "sound6"); + smgr->update(1.0); + printf("playing sample6\n"); sleep(1); - sleep(10); + for (int i=0; i<10; i++) { + sleep(1); + smgr->update(1); + } + + sgr->stop("sound1"); + sgr->stop("sound2"); + sgr->stop("sound3"); + sleep(0.5); + smgr->update(0.5); + sgr->stop("sound4"); + sgr->stop("sound5"); + sgr->stop("sound6"); + smgr->update(1); + sleep(1); + + smgr->unbind(); + sleep(2); + delete smgr; } diff --git a/simgear/sound/openal_test3.cxx b/simgear/sound/openal_test3.cxx new file mode 100644 index 00000000..96a57a04 --- /dev/null +++ b/simgear/sound/openal_test3.cxx @@ -0,0 +1,57 @@ +#include +#ifdef _WIN32 +#include +#define sleep(x) Sleep(x*1000) +#else +#include +#endif + +#include +#include + +#include "soundmgr_openal.hxx" + + +int main( int argc, char *argv[] ) { + SGSampleGroup *sgr; + SGSoundMgr *smgr; + SGGeod pos; + + smgr = new SGSoundMgr; + + smgr->bind(); + smgr->init(); + sgr = smgr->find("default", true); + smgr->set_volume(0.9); + smgr->activate(); + + printf("default position and orientation\n"); + SGSoundSample *sample1 = new SGSoundSample( SRC_DIR, "jet.wav" ); + sample1->set_volume(1.0); + sample1->set_pitch(1.0); + sample1->play_looped(); + sgr->add(sample1, "sound1"); + smgr->update(1.0); + printf("playing sample\n"); + sleep(3); + sample1->stop(); + smgr->update(3.0); + sleep(1); + + printf("source at lat,lon = (10,-10), listener at (9.99,-9.99)\n"); + pos = SGGeod::fromDeg(9.99,-9.99); + sample1->set_position( SGVec3d::fromGeod(SGGeod::fromDeg(10,-10)) ); + smgr->set_position( SGVec3d::fromGeod(pos), pos ); + sample1->play_looped(); + smgr->update(1.0); + printf("playing sample\n"); + sleep(3); + sample1->stop(); + smgr->update(3.0); + sleep(1); + + sgr->remove("sound1"); + smgr->unbind(); + sleep(2); + delete smgr; +} diff --git a/simgear/sound/sample_group.cxx b/simgear/sound/sample_group.cxx new file mode 100644 index 00000000..1da29059 --- /dev/null +++ b/simgear/sound/sample_group.cxx @@ -0,0 +1,453 @@ +// sample_group.cxx -- Manage a group of samples relative to a base position +// +// Written for the new SoundSystem by Erik Hofman, October 2009 +// +// Copyright (C) 2009 Erik Hofman +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "soundmgr_openal.hxx" +#include "sample_group.hxx" + +bool isNaN(float *v) { + return (isnan(v[0]) || isnan(v[1]) || isnan(v[2])); +} + +SGSampleGroup::SGSampleGroup () : + _smgr(NULL), + _refname(""), + _active(false), + _changed(false), + _pause(false), + _volume(1.0), + _tied_to_listener(false), + _velocity(SGVec3d::zeros()), + _orientation(SGQuatd::zeros()) +{ + _samples.clear(); +} + +SGSampleGroup::SGSampleGroup ( SGSoundMgr *smgr, const string &refname ) : + _smgr(smgr), + _refname(refname), + _active(false), + _changed(false), + _pause(false), + _volume(1.0), + _tied_to_listener(false), + _velocity(SGVec3d::zeros()), + _orientation(SGQuatd::zeros()) +{ + _smgr->add(this, refname); + _samples.clear(); +} + +SGSampleGroup::~SGSampleGroup () +{ + _active = false; + + sample_map_iterator sample_current = _samples.begin(); + sample_map_iterator sample_end = _samples.end(); + for ( ; sample_current != sample_end; ++sample_current ) { + SGSoundSample *sample = sample_current->second; + + if ( sample->is_valid_source() && sample->is_playing() ) { + sample->no_valid_source(); + _smgr->release_source( sample->get_source() ); + _smgr->release_buffer( sample ); + } + } + + _smgr = 0; +} + +void SGSampleGroup::update( double dt ) { + + if ( !_active || _pause ) return; + + testForALError("start of update!!\n"); + + // Delete any OpenAL buffers that might still be in use. + unsigned int size = _removed_samples.size(); + for (unsigned int i=0; iis_valid_source() ) { + if ( sample->is_looping() ) { + sample->no_valid_source(); + _smgr->release_source( sample->get_source() ); + } + else + alGetSourcei( sample->get_source(), AL_SOURCE_STATE, &result ); + } + + if ( result == AL_STOPPED ) { + ALuint buffer = sample->get_buffer(); + alDeleteBuffers( 1, &buffer ); + testForALError("buffer remove"); + _removed_samples.erase( _removed_samples.begin()+i ); + size--; + continue; + } + i++; + } + + // Update the position and orientation information for all samples. + if ( _changed || _smgr->has_changed() ) { + update_pos_and_orientation(); + _changed = false; + } + + sample_map_iterator sample_current = _samples.begin(); + sample_map_iterator sample_end = _samples.end(); + for ( ; sample_current != sample_end; ++sample_current ) { + SGSoundSample *sample = sample_current->second; + + if ( !sample->is_valid_source() && sample->is_playing() ) { + // + // a request to start playing a sound has been filed. + // + if ( _smgr->request_buffer(sample) == SGSoundMgr::NO_BUFFER ) + continue; + + // start playing the sample + ALuint buffer = sample->get_buffer(); + ALuint source = _smgr->request_source(); + if (alIsSource(source) == AL_TRUE && alIsBuffer(buffer) == AL_TRUE) + { + sample->set_source( source ); + + alSourcei( source, AL_BUFFER, buffer ); + testForALError("assign buffer to source"); + + sample->set_source( source ); + update_sample_config( sample ); + + ALboolean looping = sample->is_looping() ? AL_TRUE : AL_FALSE; + alSourcei( source, AL_LOOPING, looping ); + alSourcef( source, AL_ROLLOFF_FACTOR, 0.3 ); + alSourcei( source, AL_SOURCE_RELATIVE, AL_FALSE ); + alSourcePlay( source ); + testForALError("sample play"); + } else { + if (alIsBuffer(buffer) == AL_FALSE) + SG_LOG( SG_GENERAL, SG_ALERT, "No such buffer!\n"); + // sample->no_valid_source(); + // sadly, no free source available at this time + } + + } else if ( sample->is_valid_source() && sample->has_changed() ) { + if ( !sample->is_playing() ) { + // a request to stop playing the sound has been filed. + + sample->stop(); + sample->no_valid_source(); + _smgr->release_source( sample->get_source() ); + } else if ( _smgr->has_changed() ) { + update_sample_config( sample ); + } + + } else if ( sample->is_valid_source() ) { + // check if the sound has stopped by itself + + unsigned int source = sample->get_source(); + int result; + + alGetSourcei( source, AL_SOURCE_STATE, &result ); + if ( result == AL_STOPPED ) { + // sample is stoped because it wasn't looping + sample->stop(); + sample->no_valid_source(); + _smgr->release_source( source ); + _smgr->release_buffer( sample ); + remove( sample->get_sample_name() ); + } + } + testForALError("update"); + } +} + +// add a sound effect, return true if successful +bool SGSampleGroup::add( SGSharedPtr sound, const string& refname ) { + + sample_map_iterator sample_it = _samples.find( refname ); + if ( sample_it != _samples.end() ) { + // sample name already exists + return false; + } + + _samples[refname] = sound; + return true; +} + + +// remove a sound effect, return true if successful +bool SGSampleGroup::remove( const string &refname ) { + + sample_map_iterator sample_it = _samples.find( refname ); + if ( sample_it == _samples.end() ) { + // sample was not found + return false; + } + + if ( sample_it->second->is_valid_buffer() ) + _removed_samples.push_back( sample_it->second ); + _samples.erase( sample_it ); + + return true; +} + + +// return true of the specified sound exists in the sound manager system +bool SGSampleGroup::exists( const string &refname ) { + sample_map_iterator sample_it = _samples.find( refname ); + if ( sample_it == _samples.end() ) { + // sample was not found + return false; + } + + return true; +} + + +// return a pointer to the SGSoundSample if the specified sound exists +// in the sound manager system, otherwise return NULL +SGSoundSample *SGSampleGroup::find( const string &refname ) { + sample_map_iterator sample_it = _samples.find( refname ); + if ( sample_it == _samples.end() ) { + // sample was not found + return NULL; + } + + return sample_it->second; +} + + +void +SGSampleGroup::stop () +{ + _pause = true; + sample_map_iterator sample_current = _samples.begin(); + sample_map_iterator sample_end = _samples.end(); + for ( ; sample_current != sample_end; ++sample_current ) { + SGSoundSample *sample = sample_current->second; + + if ( sample->is_valid_source() ) { + ALint source = sample->get_source(); + if ( sample->is_playing() ) { + alSourceStop( source ); + alSourcei( source, AL_BUFFER, 0 ); + } + _smgr->release_source( source ); + sample->no_valid_source(); + } + + if (sample->is_valid_buffer() ) { + _smgr->release_buffer( sample ); + sample->no_valid_buffer(); + } + } + testForALError("stop"); +} + +// stop playing all associated samples +void +SGSampleGroup::suspend () +{ + if (_pause == false) { + _pause = true; + sample_map_iterator sample_current = _samples.begin(); + sample_map_iterator sample_end = _samples.end(); + for ( ; sample_current != sample_end; ++sample_current ) { + SGSoundSample *sample = sample_current->second; + + if ( sample->is_valid_source() && sample->is_playing() ) { + alSourcePause( sample->get_source() ); + } + } + testForALError("suspend"); + } +} + +// resume playing all associated samples +void +SGSampleGroup::resume () +{ + if (_pause == true) { + sample_map_iterator sample_current = _samples.begin(); + sample_map_iterator sample_end = _samples.end(); + for ( ; sample_current != sample_end; ++sample_current ) { + SGSoundSample *sample = sample_current->second; + + if ( sample->is_valid_source() && sample->is_playing() ) { + alSourcePlay( sample->get_source() ); + } + } + testForALError("resume"); + _pause = false; + } +} + + +// tell the scheduler to play the indexed sample in a continuous loop +bool SGSampleGroup::play( const string &refname, bool looping = false ) { + SGSoundSample *sample = find( refname ); + + if ( sample == NULL ) { + return false; + } + + sample->play( looping ); + return true; +} + + +// return true of the specified sound is currently being played +bool SGSampleGroup::is_playing( const string& refname ) { + SGSoundSample *sample = find( refname ); + + if ( sample == NULL ) { + return false; + } + + return ( sample->is_playing() ) ? true : false; +} + +// immediate stop playing the sound +bool SGSampleGroup::stop( const string& refname ) { + SGSoundSample *sample = find( refname ); + + if ( sample == NULL ) { + return false; + } + + sample->stop(); + return true; +} + +void SGSampleGroup::set_volume( float vol ) +{ + if (vol > _volume*1.01 || vol < _volume*0.99) { + _volume = vol; + if (_volume < 0.0) _volume = 0.0; + if (_volume > 1.0) _volume = 1.0; + _changed = true; + } +} + +// set the source position and orientation of all managed sounds +void SGSampleGroup::update_pos_and_orientation() { + + SGVec3d position = SGVec3d::fromGeod(_base_pos) - _smgr->get_position(); + SGQuatd hlOr = SGQuatd::fromLonLat(_base_pos); + SGQuatd ec2body = hlOr*_orientation; + + SGVec3f velocity = SGVec3f::zeros(); + if ( _velocity[0] || _velocity[1] || _velocity[2] ) { + velocity = toVec3f( hlOr.backTransform(_velocity*SG_FEET_TO_METER) ); + } + + sample_map_iterator sample_current = _samples.begin(); + sample_map_iterator sample_end = _samples.end(); + for ( ; sample_current != sample_end; ++sample_current ) { + SGSoundSample *sample = sample_current->second; + sample->set_master_volume( _volume ); + sample->set_orientation( _orientation ); + sample->set_rotation( ec2body ); + sample->set_position( position ); + sample->set_velocity( velocity ); + } +} + +void SGSampleGroup::update_sample_config( SGSoundSample *sample ) { + SGVec3f orientation, velocity; + SGVec3d position; + + if ( _tied_to_listener ) { + orientation = _smgr->get_direction(); + position = SGVec3d::zeros(); + velocity = _smgr->get_velocity(); + } else { + sample->update_pos_and_orientation(); + orientation = sample->get_orientation(); + position = sample->get_position(); + velocity = sample->get_velocity(); + } + + if (_smgr->bad_doppler_effect()) { + velocity *= 100.0f; + } + +#if 0 + if (length(position) > 20000) + printf("%s source and listener distance greater than 20km!\n", + _refname.c_str()); + if (isNaN(toVec3f(position).data())) printf("NaN in source position\n"); + if (isNaN(orientation.data())) printf("NaN in source orientation\n"); + if (isNaN(velocity.data())) printf("NaN in source velocity\n"); +#endif + + unsigned int source = sample->get_source(); + alSourcefv( source, AL_POSITION, toVec3f(position).data() ); + alSourcefv( source, AL_VELOCITY, velocity.data() ); + alSourcefv( source, AL_DIRECTION, orientation.data() ); + testForALError("position and orientation"); + + alSourcef( source, AL_PITCH, sample->get_pitch() ); + alSourcef( source, AL_GAIN, sample->get_volume() ); + testForALError("pitch and gain"); + + if ( sample->has_static_data_changed() ) { + alSourcef( source, AL_CONE_INNER_ANGLE, sample->get_innerangle() ); + alSourcef( source, AL_CONE_OUTER_ANGLE, sample->get_outerangle() ); + alSourcef( source, AL_CONE_OUTER_GAIN, sample->get_outergain() ); + testForALError("audio cone"); + + alSourcef( source, AL_MAX_DISTANCE, sample->get_max_dist() ); + alSourcef( source, AL_REFERENCE_DISTANCE, + sample->get_reference_dist() ); + testForALError("distance rolloff"); + } +} + +bool SGSampleGroup::testForError(void *p, string s) +{ + if (p == NULL) { + SG_LOG( SG_GENERAL, SG_ALERT, "Error (sample group): " << s); + return true; + } + return false; +} + +bool SGSampleGroup::testForALError(string s) +{ + ALenum error = alGetError(); + if (error != AL_NO_ERROR) { + SG_LOG( SG_GENERAL, SG_ALERT, "AL Error (" << _refname << "): " + << alGetString(error) << " at " << s); + return true; + } + return false; +} + diff --git a/simgear/sound/sample_group.hxx b/simgear/sound/sample_group.hxx new file mode 100644 index 00000000..d2d47394 --- /dev/null +++ b/simgear/sound/sample_group.hxx @@ -0,0 +1,249 @@ +// sample_group.hxx -- Manage a group of samples relative to a base position +// +// Written for the new SoundSystem by Erik Hofman, October 2009 +// +// Copyright (C) 2009 Erik Hofman +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ + +/** + * \file sample_group.hxx + * sample groups contain all sounds related to one specific object and + * have to be added to the sound manager, otherwise they won't get processed. + */ + +#ifndef _SG_SAMPLE_GROUP_OPENAL_HXX +#define _SG_SAMPLE_GROUP_OPENAL_HXX 1 + +#ifndef __cplusplus +# error This library requires C++ +#endif + +#if defined(__APPLE__) +# include +#elif defined(OPENALSDK) +# include +#else +# include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sample_openal.hxx" + +using std::map; +using std::string; + +typedef map < string, SGSharedPtr > sample_map; +typedef sample_map::iterator sample_map_iterator; +typedef sample_map::const_iterator const_sample_map_iterator; + +class SGSoundMgr; + +class SGSampleGroup : public SGReferenced +{ +public: + + /** + * Empty constructor for class encapsulation. + * Note: The sample group should still be activated before use + */ + SGSampleGroup (); + + /** + * Constructor + * Register this sample group at the sound manager using refname + * Note: The sample group should still be activated before use + * @param smgr Pointer to a pre-initialized sound manager class + * @param refname Name of this group for reference purposes. + */ + SGSampleGroup ( SGSoundMgr *smgr, const string &refname ); + + /** + * Destructor + */ + ~SGSampleGroup (); + + /** + * Set the status of this sample group to active. + */ + inline void activate() { _active = true; } + + /** + * Update function. + * Call this function periodically to update the OpenAL state of all + * samples associated with this class. None op the configuration changes + * take place without a call to this function. + */ + virtual void update (double dt); + + /** + * Register an audio sample to this group. + * @param sound Pointer to a pre-initialized audio sample + * @param refname Name of this audio sample for reference purposes + * @return return true if successful + */ + bool add( SGSharedPtr sound, const string& refname ); + + /** + * Remove an audio sample from this group. + * @param refname Reference name of the audio sample to remove + * @return return true if successful + */ + bool remove( const string& refname ); + + /** + * Test if a specified audio sample is registered at this sample group + * @param refname Reference name of the audio sample to test for + * @return true if the specified audio sample exists + */ + bool exists( const string& refname ); + + /** + * Find a specified audio sample in this sample group + * @param refname Reference name of the audio sample to find + * @return A pointer to the SGSoundSample + */ + SGSoundSample *find( const string& refname ); + + /** + * Stop all playing samples and set the source id to invalid. + */ + void stop(); + + /** + * Request to stop playing all audio samples until further notice. + */ + void suspend(); + + /** + * Request to resume playing all audio samples. + */ + void resume(); + + /** + * Request to start playing the refered audio sample. + * @param refname Reference name of the audio sample to start playing + * @param looping Define if the sound should loop continuously + * @return true if the audio sample exsists and is scheduled for playing + */ + bool play( const string& refname, bool looping ); + + /** + * Request to start playing the refered audio sample looping. + * @param refname Reference name of the audio sample to start playing + * @return true if the audio sample exsists and is scheduled for playing + */ + inline bool play_looped( const string& refname ) { + return play( refname, true ); + } + + /** + * Request to start playing the refered audio sample once. + * @param refname Reference name of the audio sample to start playing + * @return true if the audio sample exsists and is scheduled for playing + */ + inline bool play_once( const string& refname ) { + return play( refname, false ); + } + + /** + * Test if a referenced sample is playing or not. + * @param refname Reference name of the audio sample to test for + * @return True of the specified sound is currently playing + */ + bool is_playing( const string& refname ); + + /** + * Request to stop playing the refered audio sample. + * @param refname Reference name of the audio sample to stop + * @return true if the audio sample exsists and is scheduled to stop + */ + bool stop( const string& refname ); + + /** + * Set the master volume for this sample group. + * @param vol Volume (must be between 0.0 and 1.0) + */ + void set_volume( float vol ); + + /** + * Set the velocity vector of this sample group. + * This is in the local frame coordinate system; x=north, y=east, z=down + * @param vel Velocity vector + */ + void set_velocity( const SGVec3d& vel ) { + _velocity = vel; _changed = true; + } + + /** + * Set the position of this sample group. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right. + * @param pos Base position + */ + void set_position_geod( const SGGeod& pos ) { + _base_pos = pos; _changed = true; + } + + /** + * Set the orientation of this sample group. + * @param ori Quaternation containing the orientation information + */ + void set_orientation( const SGQuatd& ori ) { + _orientation = ori; _changed = true; + } + + /** + * Tie this sample group to the listener position, orientation and velocity + */ + void tie_to_listener() { _tied_to_listener = true; } + +protected: + SGSoundMgr *_smgr; + string _refname; + bool _active; + +private: + bool _changed; + bool _pause; + float _volume; + bool _tied_to_listener; + + SGVec3d _velocity; + SGGeod _base_pos; + SGQuatd _orientation; + + sample_map _samples; + std::vector< SGSharedPtr > _removed_samples; + + bool testForALError(string s); + bool testForError(void *p, string s); + + void update_pos_and_orientation(); + void update_sample_config( SGSoundSample *sound ); +}; + +#endif // _SG_SAMPLE_GROUP_OPENAL_HXX + diff --git a/simgear/sound/sample_openal.cxx b/simgear/sound/sample_openal.cxx index 52eaea18..16291d7d 100644 --- a/simgear/sound/sample_openal.cxx +++ b/simgear/sound/sample_openal.cxx @@ -1,8 +1,10 @@ -// sample.cxx -- Sound sample encapsulation class +// sample_openal.cxx -- Audio sample encapsulation class // // Written by Curtis Olson, started April 2004. +// Modified to match the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt +// Copyright (C) 2009 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -15,8 +17,8 @@ // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ @@ -24,20 +26,14 @@ # include #endif -#if defined( __APPLE__ ) -# define AL_ILLEGAL_ENUM AL_INVALID_ENUM -# define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION -# include -# include -#else -# include -# include -#endif +#include // rand() #include -#include #include +#include +#include +#include "soundmgr_openal.hxx" #include "sample_openal.hxx" @@ -45,477 +41,181 @@ // SGSoundSample // - -static bool print_openal_error(const string &s = "unknown") { - ALuint error = alGetError(); - if ( error == AL_NO_ERROR ) { - return false; - } else if ( error == AL_INVALID_NAME ) { - SG_LOG( SG_GENERAL, SG_ALERT, "OpenAL error (AL_INVALID_NAME): " << s ); - } else if ( error == AL_ILLEGAL_ENUM ) { - SG_LOG( SG_GENERAL, SG_ALERT, "OpenAL error (AL_ILLEGAL_ENUM): " << s ); - } else if ( error == AL_INVALID_VALUE ) { - SG_LOG( SG_GENERAL, SG_ALERT, "OpenAL error (AL_INVALID_VALUE): " << s ); - } else if ( error == AL_ILLEGAL_COMMAND ) { - SG_LOG( SG_GENERAL, SG_ALERT, "OpenAL error (AL_ILLEGAL_COMMAND): " << s ); - } else if ( error == AL_OUT_OF_MEMORY ) { - SG_LOG( SG_GENERAL, SG_ALERT, "OpenAL error (AL_OUT_OF_MEMORY): " << s ); - } else { - SG_LOG( SG_GENERAL, SG_ALERT, "Unhandled error code = " << error ); - } - return error != 0; -} - // empty constructor SGSoundSample::SGSoundSample() : - buffer(0), - source(0), - pitch(1.0), - volume(1.0), - reference_dist(500.0), - max_dist(3000.), - loop(AL_FALSE), -#ifdef USE_SOFTWARE_DOPPLER - doppler_pitch_factor(1), - doppler_volume_factor(1), -#endif - playing(false), - no_Doppler_effect(true) + _absolute_pos(SGVec3d::zeros()), + _relative_pos(SGVec3d::zeros()), + _direction(SGVec3d::zeros()), + _velocity(SGVec3f::zeros()), + _orientation(SGQuatd::zeros()), + _orivec(SGVec3f::zeros()), + _base_pos(SGVec3d::zeros()), + _rotation(SGQuatd::zeros()), + _refname(random_string()), + _data(NULL), + _format(AL_FORMAT_MONO8), + _size(0), + _freq(0), + _valid_buffer(false), + _buffer(SGSoundMgr::NO_BUFFER), + _valid_source(false), + _source(SGSoundMgr::NO_SOURCE), + _inner_angle(360.0), + _outer_angle(360.0), + _outer_gain(0.0), + _pitch(1.0), + _volume(1.0), + _master_volume(1.0), + _reference_dist(500.0), + _max_dist(3000.0), + _loop(AL_FALSE), + _playing(false), + _changed(true), + _static_changed(true), + _is_file(false) { } // constructor -SGSoundSample::SGSoundSample( const char *path, const char *file, bool _no_Doppler_effect ) : - buffer(0), - source(0), - pitch(1.0), - volume(1.0), - reference_dist(500.0), - max_dist(3000.), - loop(AL_FALSE), -#ifdef USE_SOFTWARE_DOPPLER - doppler_pitch_factor(1), - doppler_volume_factor(1), -#endif - playing(false), - no_Doppler_effect(_no_Doppler_effect) +SGSoundSample::SGSoundSample( const char *path, const char *file ) : + _absolute_pos(SGVec3d::zeros()), + _relative_pos(SGVec3d::zeros()), + _direction(SGVec3d::zeros()), + _velocity(SGVec3f::zeros()), + _orientation(SGQuatd::zeros()), + _orivec(SGVec3f::zeros()), + _base_pos(SGVec3d::zeros()), + _rotation(SGQuatd::zeros()), + _refname(file), + _data(NULL), + _format(AL_FORMAT_MONO8), + _size(0), + _freq(0), + _valid_buffer(false), + _buffer(SGSoundMgr::NO_BUFFER), + _valid_source(false), + _source(SGSoundMgr::NO_SOURCE), + _inner_angle(360.0), + _outer_angle(360.0), + _outer_gain(0.0), + _pitch(1.0), + _volume(1.0), + _master_volume(1.0), + _reference_dist(500.0), + _max_dist(3000.0), + _loop(AL_FALSE), + _playing(false), + _changed(true), + _static_changed(true), + _is_file(true) { SGPath samplepath( path ); if ( strlen(file) ) { samplepath.append( file ); } - sample_name = samplepath.str(); - - SG_LOG( SG_GENERAL, SG_DEBUG, "From file sounds sample = " - << samplepath.str() ); - - source_pos[0] = 0.0; source_pos[1] = 0.0; source_pos[2] = 0.0; - offset_pos[0] = 0.0; offset_pos[1] = 0.0; offset_pos[2] = 0.0; - source_vel[0] = 0.0; source_vel[1] = 0.0; source_vel[2] = 0.0; - direction[0] = 0.0; direction[1] = 0.0; direction[2] = 0.0; - inner = outer = 360.0; outergain = 0.0; - - // clear errors from elsewhere? - alGetError(); - - // create an OpenAL buffer handle - alGenBuffers(1, &buffer); - if ( print_openal_error("constructor (alGenBuffers)") ) { - throw sg_exception("Failed to gen OpenAL buffer."); - } - - // Load the sample file -#if defined(ALUT_API_MAJOR_VERSION) && ALUT_API_MAJOR_VERSION >= 1 - - buffer = alutCreateBufferFromFile(samplepath.c_str()); - if (buffer == AL_NONE) { - ALenum error = alutGetError (); - print_openal_error("constructor (alutCreateBufferFromFile)"); - throw sg_io_exception("Failed to load wav file: ", - sg_location(string(alutGetErrorString (error)))); - } - -#else - // - // pre 1.0 alut version - // - ALvoid* data = load_file(path, file); - - // Copy data to the internal OpenAL buffer - alBufferData( buffer, format, data, size, freq ); - - if ( print_openal_error("constructor (alBufferData)") ) { - SG_LOG( SG_GENERAL, SG_ALERT, "Trying to use file " << file ); - throw sg_exception("Failed to buffer data."); - } - - alutUnloadWAV( format, data, size, freq ); -#endif - - print_openal_error("constructor return"); + _refname = samplepath.str(); } // constructor -SGSoundSample::SGSoundSample( unsigned char *_data, int len, int _freq, bool _no_Doppler_effect ) : - buffer(0), - source(0), - pitch(1.0), - volume(1.0), - reference_dist(500.0), - max_dist(3000.), - loop(AL_FALSE), -#ifdef USE_SOFTWARE_DOPPLER - doppler_pitch_factor(1), - doppler_volume_factor(1), -#endif - playing(false), - no_Doppler_effect(_no_Doppler_effect) +SGSoundSample::SGSoundSample( const unsigned char** data, + int len, int freq, int format ) : + _absolute_pos(SGVec3d::zeros()), + _relative_pos(SGVec3d::zeros()), + _direction(SGVec3d::zeros()), + _velocity(SGVec3f::zeros()), + _orientation(SGQuatd::zeros()), + _orivec(SGVec3f::zeros()), + _base_pos(SGVec3d::zeros()), + _rotation(SGQuatd::zeros()), + _refname(random_string()), + _format(format), + _size(len), + _freq(freq), + _valid_buffer(false), + _buffer(SGSoundMgr::NO_BUFFER), + _valid_source(false), + _source(SGSoundMgr::NO_SOURCE), + _inner_angle(360.0), + _outer_angle(360.0), + _outer_gain(0.0), + _pitch(1.0), + _volume(1.0), + _master_volume(1.0), + _reference_dist(500.0), + _max_dist(3000.0), + _loop(AL_FALSE), + _playing(false), + _changed(true), + _static_changed(true), + _is_file(false) { SG_LOG( SG_GENERAL, SG_DEBUG, "In memory sounds sample" ); + _data = (unsigned char*)*data; *data = NULL; +} - sample_name = "unknown, generated from data"; - - source_pos[0] = 0.0; source_pos[1] = 0.0; source_pos[2] = 0.0; - offset_pos[0] = 0.0; offset_pos[1] = 0.0; offset_pos[2] = 0.0; - source_vel[0] = 0.0; source_vel[1] = 0.0; source_vel[2] = 0.0; - direction[0] = 0.0; direction[1] = 0.0; direction[2] = 0.0; - inner = outer = 360.0; outergain = 0.0; - - // clear errors from elsewhere? - alGetError(); - - // Load wav data into a buffer. - alGenBuffers(1, &buffer); - if ( print_openal_error("constructor (alGenBuffers)") ) { - throw sg_exception("Failed to gen buffer." ); - } - - format = AL_FORMAT_MONO8; - size = len; - freq = _freq; - - alBufferData( buffer, format, _data, size, freq ); - if ( print_openal_error("constructor (alBufferData)") ) { - throw sg_exception("Failed to buffer data."); - } - - print_openal_error("constructor return"); +// constructor +SGSoundSample::SGSoundSample( void** data, int len, int freq, int format ) : + _absolute_pos(SGVec3d::zeros()), + _relative_pos(SGVec3d::zeros()), + _direction(SGVec3d::zeros()), + _velocity(SGVec3f::zeros()), + _orientation(SGQuatd::zeros()), + _orivec(SGVec3f::zeros()), + _base_pos(SGVec3d::zeros()), + _rotation(SGQuatd::zeros()), + _refname(random_string()), + _format(format), + _size(len), + _freq(freq), + _valid_buffer(false), + _buffer(SGSoundMgr::NO_BUFFER), + _valid_source(false), + _source(SGSoundMgr::NO_SOURCE), + _inner_angle(360.0), + _outer_angle(360.0), + _outer_gain(0.0), + _pitch(1.0), + _volume(1.0), + _master_volume(1.0), + _reference_dist(500.0), + _max_dist(3000.0), + _loop(AL_FALSE), + _playing(false), + _changed(true), + _static_changed(true), + _is_file(false) +{ + SG_LOG( SG_GENERAL, SG_DEBUG, "In memory sounds sample" ); + _data = (unsigned char*)*data; *data = NULL; } // destructor SGSoundSample::~SGSoundSample() { - SG_LOG( SG_GENERAL, SG_INFO, "Deleting a sample" ); - if (buffer) - alDeleteBuffers(1, &buffer); + if ( _data != NULL ) free(_data); } +void SGSoundSample::update_pos_and_orientation() { -// play the sample -void SGSoundSample::play( bool _loop ) { - - if ( source ) { - alSourceStop( source ); + _absolute_pos = _base_pos; + if (_relative_pos[0] || _relative_pos[1] || _relative_pos[2] ) { + _absolute_pos += _rotation.rotate( _relative_pos ); } - playing = bind_source(); - if ( playing ) { - loop = _loop; - - alSourcei( source, AL_LOOPING, loop ); - alSourcePlay( source ); - - print_openal_error("play (alSourcePlay)"); + _orivec = SGVec3f::zeros(); + if ( _direction[0] || _direction[1] || _direction[2] ) { + _orivec = toVec3f( _rotation.rotate( _direction ) ); } } +string SGSoundSample::random_string() { + static const char *r = "0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + string rstr = "System generated name: "; + for (int i=0; i<10; i++) { + rstr.push_back( r[rand() % strlen(r)] ); + } -// stop playing the sample -void SGSoundSample::stop() { - if (playing) { - alSourceStop( source ); - alDeleteSources(1, &source); - source = 0; - print_openal_error("stop (alDeleteSources)"); - } - playing = false; -} - -// Generate sound source -bool -SGSoundSample::bind_source() { - - if ( playing ) { - return true; - } - if ( buffer == 0 ) { - return false; - } - - // Bind buffer with a source. - alGetError(); - alGenSources(1, &source); - if ( print_openal_error("bind_source (alGenSources)") ) { - // No biggy, better luck next time. - SG_LOG( SG_GENERAL, SG_ALERT, "Failed to generate audio source."); - // SG_LOG( SG_GENERAL, SG_ALERT, "Please update your sound driver and try again."); - return false; - } - - alSourcei( source, AL_BUFFER, buffer ); -#ifndef USE_SOFTWARE_DOPPLER - alSourcef( source, AL_PITCH, pitch ); - alSourcef( source, AL_GAIN, volume ); -#else - print_openal_error("bind_sources return"); - alSourcef( source, AL_PITCH, pitch * doppler_pitch_factor ); - alGetError(); // ignore if the pitch is clamped by the driver - alSourcef( source, AL_GAIN, volume * doppler_volume_factor ); -#endif - alSourcefv( source, AL_POSITION, source_pos ); - alSourcefv( source, AL_DIRECTION, direction ); - alSourcef( source, AL_CONE_INNER_ANGLE, inner ); - alSourcef( source, AL_CONE_OUTER_ANGLE, outer ); - alSourcef( source, AL_CONE_OUTER_GAIN, outergain); -#ifdef USE_OPEN_AL_DOPPLER - alSourcefv( source, AL_VELOCITY, source_vel ); -#endif - alSourcei( source, AL_LOOPING, loop ); - - alSourcei( source, AL_SOURCE_RELATIVE, AL_TRUE ); - alSourcef( source, AL_REFERENCE_DISTANCE, reference_dist ); - alSourcef( source, AL_MAX_DISTANCE, max_dist ); - - print_openal_error("bind_sources return"); - - return true; -} - -void -SGSoundSample::set_pitch( double p ) { - // clamp in the range of 0.01 to 2.0 - if ( p < 0.01 ) { p = 0.01; } - if ( p > 2.0 ) { p = 2.0; } - pitch = p; - if (playing) { -#ifndef USE_SOFTWARE_DOPPLER - alSourcef( source, AL_PITCH, pitch ); - print_openal_error("set_pitch"); -#else - alSourcef( source, AL_PITCH, pitch * doppler_pitch_factor ); - alGetError(); // ignore if the pitch is clamped by the driver -#endif - } -} - -void -SGSoundSample::set_volume( double v ) { - volume = v; - if (playing) { -#ifndef USE_SOFTWARE_DOPPLER - alSourcef( source, AL_GAIN, volume ); -#else - alSourcef( source, AL_GAIN, volume * doppler_volume_factor ); -#endif - print_openal_error("set_volume"); - } -} - - -bool -SGSoundSample::is_playing( ) { - if (playing) { - ALint result; - alGetSourcei( source, AL_SOURCE_STATE, &result ); - if ( alGetError() != AL_NO_ERROR) { - SG_LOG( SG_GENERAL, SG_ALERT, - "Oops AL error in sample is_playing(): " << sample_name ); - } - return (result == AL_PLAYING) ; - } else - return false; -} - -void -SGSoundSample::set_source_pos( ALfloat *pos ) { - source_pos[0] = pos[0]; - source_pos[1] = pos[1]; - source_pos[2] = pos[2]; - - if (playing) { - sgVec3 final_pos; - sgAddVec3( final_pos, source_pos, offset_pos ); - - alSourcefv( source, AL_POSITION, final_pos ); - print_openal_error("set_source_pos"); - } -} - -void -SGSoundSample::set_offset_pos( ALfloat *pos ) { - offset_pos[0] = pos[0]; - offset_pos[1] = pos[1]; - offset_pos[2] = pos[2]; - - if (playing) { - sgVec3 final_pos; - sgAddVec3( final_pos, source_pos, offset_pos ); - - alSourcefv( source, AL_POSITION, final_pos ); - print_openal_error("set_offset_pos"); - } -} - -void -SGSoundSample::set_orientation( ALfloat *dir, ALfloat inner_angle, - ALfloat outer_angle, - ALfloat outer_gain) -{ - inner = inner_angle; - outer = outer_angle; - outergain = outer_gain; - direction[0] = dir[0]; - direction[1] = dir[1]; - direction[2] = dir[2]; - if (playing) { - alSourcefv( source, AL_DIRECTION, dir); - alSourcef( source, AL_CONE_INNER_ANGLE, inner ); - alSourcef( source, AL_CONE_OUTER_ANGLE, outer ); - alSourcef( source, AL_CONE_OUTER_GAIN, outergain ); - } -} - -void -SGSoundSample::set_source_vel( ALfloat *vel, ALfloat *listener_vel ) { - if (no_Doppler_effect) { - source_vel[0] = listener_vel[0]; - source_vel[1] = listener_vel[1]; - source_vel[2] = listener_vel[2]; - } else { - source_vel[0] = vel[0]; - source_vel[1] = vel[1]; - source_vel[2] = vel[2]; - } -#ifdef USE_OPEN_AL_DOPPLER - if (playing) { - alSourcefv( source, AL_VELOCITY, source_vel ); - } -#elif defined (USE_OPEN_AL_DOPPLER_WITH_FIXED_LISTENER) - if (playing) { - sgVec3 relative_vel; - sgSubVec3( relative_vel, source_vel, listener_vel ); - alSourcefv( source, AL_VELOCITY, relative_vel ); - } -#else - if (no_Doppler_effect) { - doppler_pitch_factor = 1; - doppler_volume_factor = 1; - return; - } - double doppler, mfp; - sgVec3 final_pos; - sgAddVec3( final_pos, source_pos, offset_pos ); - mfp = sgLengthVec3(final_pos); - if (mfp > 1e-6) { - double vls = -sgScalarProductVec3( listener_vel, final_pos ) / mfp; - double vss = -sgScalarProductVec3( source_vel, final_pos ) / mfp; - if (fabs(340 - vss) > 1e-6) - { - doppler = (340 - vls) / (340 - vss); - doppler = ( doppler > 0) ? ( ( doppler < 10) ? doppler : 10 ) : 0; - } - else - doppler = 0; - } - else - doppler = 1; - /* the OpenAL documentation of the Doppler calculation - SS: AL_SPEED_OF_SOUND = speed of sound (default value 343.3) - DF: AL_DOPPLER_FACTOR = Doppler factor (default 1.0) - vls: Listener velocity scalar (scalar, projected on source-to-listener vector) - vss: Source velocity scalar (scalar, projected on source-to-listener vector) - SL = source to listener vector - SV = Source Velocity vector - LV = Listener Velocity vector - vls = DotProduct(SL, LV) / Mag(SL) - vss = DotProduct(SL, SV) / Mag(SL) - Dopper Calculation: - vss = min(vss, SS/DF) - vls = min(vls, SS/DF) - f' = f * (SS - DF*vls) / (SS - DF*vss) - */ - if (doppler > 0.1) { - if (doppler < 10) { - doppler_pitch_factor = doppler; - doppler_volume_factor = 1; - } - else { - doppler_pitch_factor = (doppler < 11) ? doppler : 11; - doppler_volume_factor = (doppler < 11) ? 11-doppler : 0; - } - } - else { - doppler_pitch_factor = 0.1; - doppler_volume_factor = (doppler > 0) ? doppler * 10 : 0; - } - if (playing) { - alSourcef( source, AL_GAIN, volume * doppler_volume_factor ); - print_openal_error("set_source_vel: volume"); - alSourcef( source, AL_PITCH, pitch * doppler_pitch_factor ); - alGetError(); //ignore if the pitch is clamped - } -#endif -} - -void -SGSoundSample::set_reference_dist( ALfloat dist ) { - reference_dist = dist; - if (playing) { - alSourcef( source, AL_REFERENCE_DISTANCE, reference_dist ); - } -} - - -void -SGSoundSample::set_max_dist( ALfloat dist ) { - max_dist = dist; - if (playing) { - alSourcef( source, AL_MAX_DISTANCE, max_dist ); - } -} - -ALvoid * -SGSoundSample::load_file(const char *path, const char *file) -{ - ALvoid* data = 0; - - SGPath samplepath( path ); - if ( strlen(file) ) { - samplepath.append( file ); - } - -#if defined(ALUT_API_MAJOR_VERSION) && ALUT_API_MAJOR_VERSION >= 1 - ALfloat freqf; - data = alutLoadMemoryFromFile(samplepath.c_str(), &format, &size, &freqf ); - if (data == NULL) { - throw sg_io_exception("Failed to load wav file.", - sg_location(samplepath.str())); - } - freq = (ALsizei)freqf; -#else -# if defined (__APPLE__) - alutLoadWAVFile( (ALbyte *)samplepath.c_str(), - &format, &data, &size, &freq ); -# else - alutLoadWAVFile( (ALbyte *)samplepath.c_str(), - &format, &data, &size, &freq, &loop ); -# endif - if ( print_openal_error("constructor (alutLoadWAVFile)") ) { - throw sg_io_exception("Failed to load wav file.", - sg_location(samplepath.str())); - } -#endif - - return data; + return rstr; } diff --git a/simgear/sound/sample_openal.hxx b/simgear/sound/sample_openal.hxx index 1131981b..d2cb1824 100644 --- a/simgear/sound/sample_openal.hxx +++ b/simgear/sound/sample_openal.hxx @@ -1,8 +1,10 @@ -// sample.hxx -- Sound sample encapsulation class +// sample_openal.hxx -- Audio sample encapsulation class // // Written by Curtis Olson, started April 2004. +// Modified to match the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt +// Copyright (C) 2009 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -15,14 +17,14 @@ // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ /** - * \file sample.hxx - * Provides a sound sample encapsulation + * \file audio sample.hxx + * Provides a audio sample encapsulation */ #ifndef _SG_SAMPLE_HXX @@ -32,87 +34,21 @@ # error This library requires C++ #endif -#include - #include +#include #include #include #include +#include -#include - -#if defined(__APPLE__) -# define AL_ILLEGAL_ENUM AL_INVALID_ENUM -# define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION -# include -# include -#else -# include -# include -#endif - -#ifndef HAVE_WINDOWS_H - #ifdef AL_VERSION_1_2 - #define USE_OPEN_AL_DOPPLER should work - #else - #define USE_OPEN_AL_DOPPLER_WITH_FIXED_LISTENER better than nothing - #endif -#else - // the Open_AL Doppler calculation seems to be buggy on windows - #define USE_SOFTWARE_DOPPLER seem to be necessary -#endif - -using std::string; +// #include /** - * manages everything we need to know for an individual sound sample + * manages everything we need to know for an individual audio sample */ class SGSoundSample : public SGReferenced { - -private: - - string sample_name; - - // Buffers hold sound data. - ALuint buffer; - - // Sources are points emitting sound. - ALuint source; - - // Position of the source sound. - ALfloat source_pos[3]; - - // A constant offset to be applied to the final source_pos - ALfloat offset_pos[3]; - - // The orientation of the sound (direction and cut-off angles) - ALfloat direction[3]; - ALfloat inner, outer, outergain; - - // Velocity of the source sound. - ALfloat source_vel[3]; - - // configuration values - ALenum format; - ALsizei size; - ALsizei freq; - - double pitch; - double volume; -#ifdef USE_SOFTWARE_DOPPLER - double doppler_pitch_factor; - double doppler_volume_factor; -#endif - double reference_dist; - double max_dist; - ALboolean loop; - - bool playing; - bool bind_source(); - bool no_Doppler_effect; - public: /** @@ -125,125 +61,439 @@ public: * Constructor * @param path Path name to sound * @param file File name of sound - should usually be true unless you want to manipulate the data - later.) + Buffer data is freed by the sample group */ - SGSoundSample( const char *path, const char *file, bool no_Doppler_effect = true ); + SGSoundSample( const char *path, const char *file ); /** * Constructor. - * @param _data Pointer to a memory buffer containing the sample data - the application is responsible for freeing the buffer data. + * @param data Pointer to a memory buffer containing this audio sample data + The application may free the data by calling free_data(), otherwise it + will be resident untill the class is destroyed. This pointer will be + set to NULL after calling this function. * @param len Byte length of array - * @param _freq Frequency of the provided data (bytes per second) - should usually be true unless you want to manipulate the data - later.) + * @param freq Frequency of the provided data (bytes per second) + * @param format OpenAL format id of the data */ - SGSoundSample( unsigned char *_data, int len, int _freq, bool no_Doppler_effect = true ); - - ~SGSoundSample(); + SGSoundSample( void** data, int len, int freq, int format=AL_FORMAT_MONO8 ); + SGSoundSample( const unsigned char** data, int len, int freq, + int format = AL_FORMAT_MONO8 ); /** - * Start playing this sample. - * - * @param _loop Define whether the sound should be played in a loop. + * Destructor */ - void play( bool _loop ); + ~SGSoundSample (); /** - * Stop playing this sample. - * - * @param sched A pointer to the appropriate scheduler. + * Detect wheter this audio sample holds the information of a sound file. + * @return Return true if this audio sample is to be constructed from a file. */ - void stop(); + inline bool is_file() const { return _is_file; } /** - * Play this sample once. + * Test if this audio sample configuration has changed since the last call. + * Calling this function will reset the flag so calling it a second + * time in a row will return false. + * @return Return true is the configuration has changed in the mean time. + */ + bool has_changed() { + bool b = _changed; _changed = false; return b; + } + + /** + * Test if static dataa of audio sample configuration has changed. + * Calling this function will reset the flag so calling it a second + * time in a row will return false. + * @return Return true is the static data has changed in the mean time. + */ + bool has_static_data_changed() { + bool b = _static_changed; _static_changed = false; return b; + } + + /** + * Schedule this audio sample for playing. Actual playing will only start + * at the next call op SoundGroup::update() + * @param _loop Define whether this sound should be played in a loop. + */ + void play( bool loop ) { + _playing = true; _loop = loop; _changed = true; + } + + /** + * Check if this audio sample is set to be continuous looping. + * @return Return true if this audio sample is set to looping. + */ + inline bool is_looping() { return _loop; } + + /** + * Schedule this audio sample to stop playing. + */ + void stop() { + _playing = false; _changed = true; + } + + /** + * Schedule this audio sample to play once. * @see #play */ inline void play_once() { play(false); } /** - * Play this sample looped. + * Schedule this audio sample to play looped. * @see #play */ inline void play_looped() { play(true); } /** - * Test if a sample is currently playing. - * @return true if is is playing, false otherwise. + * Test if a audio sample is scheduled for playing. + * @return true if this audio sample is playing, false otherwise. */ - bool is_playing( ); + inline bool is_playing() { return _playing; } /** - * Get the current pitch setting of this sample. + * Set the data associated with this audio sample + * @param data Pointer to a memory block containg this audio sample data. + This pointer will be set to NULL after calling this function. */ - inline double get_pitch() const { return pitch; } - - /** - * Set the pitch of this sample. - */ - void set_pitch( double p ); - - /** - * Get the current volume setting of this sample. - */ - inline double get_volume() const { return volume; } - - /** - * Set the volume of this sample. - */ - void set_volume( double v ); - - /** - * Returns the size of the sounds sample - */ - inline int get_size() { - return size; + inline void set_data( const unsigned char **data ) { + _data = (unsigned char*)*data; *data = NULL; + } + inline void set_data( void **data ) { + _data = (unsigned char*)*data; *data = NULL; } /** - * Set position of sound source (uses same coordinate system as opengl) + * Return the data associated with this audio sample. + * @return A pointer to this sound data of this audio sample. */ - void set_source_pos( ALfloat *pos ); + inline void* get_data() const { return _data; } /** - * Set "constant" offset position of sound source (uses same - * coordinate system as opengl) + * Free the data associated with this audio sample */ - void set_offset_pos( ALfloat *pos ); + void free_data() { + if ( _data != NULL ) free( _data ); _data = NULL; + } /** - * Set the orientation of the sound source, both for direction - * and audio cut-off angles. + * Set the source id of this source + * @param sid OpenAL source-id */ - void set_orientation( ALfloat *dir, ALfloat inner_angle=360.0, - ALfloat outer_angle=360.0, - ALfloat outer_gain=0.0); + void set_source(unsigned int sid) { + _source = sid; _valid_source = true; _changed = true; + } /** - * Set velocity of sound source (uses same coordinate system as opengl) + * Get the OpenAL source id of this source + * @return OpenAL source-id */ - void set_source_vel( ALfloat *vel, ALfloat *listener_vel ); + inline unsigned int get_source() { return _source; } + + /** + * Test if the source-id of this audio sample may be passed to OpenAL. + * @return true if the source-id is valid + */ + inline bool is_valid_source() const { return _valid_source; } + + /** + * Set the source-id of this audio sample to invalid. + */ + inline void no_valid_source() { _valid_source = false; } + + /** + * Set the OpenAL buffer-id of this source + * @param bid OpenAL buffer-id + */ + void set_buffer(unsigned int bid) { + _buffer = bid; _valid_buffer = true; _changed = true; + } + + /** + * Get the OpenAL buffer-id of this source + * @return OpenAL buffer-id + */ + inline unsigned int get_buffer() { return _buffer; } + + /** + * Test if the buffer-id of this audio sample may be passed to OpenAL. + * @return true if the buffer-id is valid + */ + inline bool is_valid_buffer() const { return _valid_buffer; } + + /** + * Set the buffer-id of this audio sample to invalid. + */ + inline void no_valid_buffer() { _valid_buffer = false; } + + /** + * Set the playback pitch of this audio sample. + * Should be between 0.0 and 2.0 for maximum compatibility. + * @param p Pitch + */ + inline void set_pitch( float p ) { + if (p > 2.0) p = 2.0; else if (p < 0.01) p = 0.01; + _pitch = p; _changed = true; + } + + /** + * Get the current pitch value of this audio sample. + * @return Pitch + */ + inline float get_pitch() { return _pitch; } + + /** + * Set the master volume of this sample. Should be between 0.0 and 1.0. + * The final volume is calculated by multiplying the master and audio sample + * volume. + * @param v Volume + */ + inline void set_master_volume( float v ) { + if (v > 1.0) v = 1.0; else if (v < 0.0) v = 0.0; + _master_volume = v; _changed = true; + } + + /** + * Set the volume of this audio sample. Should be between 0.0 and 1.0. + * The final volume is calculated by multiplying the master and audio sample + * volume. + * @param v Volume + */ + inline void set_volume( float v ) { + if (v > 1.0) v = 1.0; else if (v < 0.0) v = 0.0; + _volume = v; _changed = true; + } + + /** + * Get the final volume value of this audio sample. + * @return Volume + */ + inline float get_volume() { return _volume * _master_volume; } + + /** + * Set the OpenAL format of this audio sample. + * @param format OpenAL format-id + */ + inline void set_format( int format ) { _format = format; } + + /** + * Returns the format of this audio sample. + * @return OpenAL format-id + */ + inline int get_format() { return _format; } + + /** + * Set the frequency (in Herz) of this audio sample. + * @param freq Frequency + */ + inline void set_frequency( int freq ) { _freq = freq; } + + /** + * Returns the frequency (in Herz) of this audio sample. + * @return Frequency + */ + inline int get_frequency() { return _freq; } + + /** + * Sets the size (in bytes) of this audio sample. + * @param size Data size + */ + inline void set_size( size_t size ) { _size = size; } + + /** + * Returns the size (in bytes) of this audio sample. + * @return Data size + */ + inline size_t get_size() const { return _size; } + + /** + * Set the position of this sound relative to the base position. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right. + * @param pos Relative position of this sound + */ + inline void set_relative_position( const SGVec3f& pos ) { + _relative_pos = toVec3d(pos); _changed = true; + } + + /** + * Set the base position in Cartesian coordinates + * @param pos position in Cartesian coordinates + */ + inline void set_position( const SGVec3d& pos ) { + _base_pos = pos; _changed = true; + } + + /** + * Get the absolute position of this sound. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right. + * @return Absolute position + */ + SGVec3d& get_position() { return _absolute_pos; } + + /** + * Set the orientation of this sound. + * @param ori Quaternation containing the orientation information + */ + inline void set_orientation( const SGQuatd& ori ) { + _orientation = ori; _changed = true; + } + + inline void set_rotation( const SGQuatd& ec2body ) { + _rotation = ec2body; _changed = true; + } + + /** + * Set direction of this sound relative to the orientation. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right + * @param dir Sound emission direction + */ + inline void set_direction( const SGVec3f& dir ) { + _direction = toVec3d(dir); _static_changed = true; + } + + /** + * Define the audio cone parameters for directional audio. + * Note: setting it to 2 degree will result in 1 degree to both sides. + * @param inner Inner cone angle (0 - 360 degrees) + * @param outer Outer cone angle (0 - 360 degrees) + * @param gain Remaining gain at the edge of the outer cone (0.0 - 1.0) + */ + void set_audio_cone( float inner, float outer, float gain ) { + _inner_angle = inner; _outer_angle = outer; _outer_gain = gain; + _static_changed = true; + } + + /** + * Get the orientation vector of this sound. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right + * @return Orientaton vector + */ + SGVec3f& get_orientation() { return _orivec; } + + /** + * Get the inner angle of the audio cone. + * @return Inner angle in degrees + */ + float get_innerangle() { return _inner_angle; } + + /** + * Get the outer angle of the audio cone. + * @return Outer angle in degrees + */ + float get_outerangle() { return _outer_angle; } + + /** + * Get the remaining gain at the edge of the outer cone. + * @return Gain + */ + float get_outergain() { return _outer_gain; } + + /** + * Set the velocity vector (in meters per second) of this sound. + * This is in the local frame coordinate system; x=north, y=east, z=down + * @param Velocity vector + */ + inline void set_velocity( const SGVec3f& vel ) { + _velocity = vel; _changed = true; + } + + /** + * Get velocity vector (in meters per second) of this sound. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right + * @return Velocity vector + */ + SGVec3f& get_velocity() { return _velocity; } /** - * Set reference distance of sound (the distance where the gain - * will be half.) + * Set reference distance (in meters) of this sound. + * This is the distance where the gain will be half. + * @param dist Reference distance */ - void set_reference_dist( ALfloat dist ); + inline void set_reference_dist( float dist ) { + _reference_dist = dist; _static_changed = true; + } + + /** + * Get reference distance ((in meters) of this sound. + * This is the distance where the gain will be half. + * @return Reference distance + */ + inline float get_reference_dist() { return _reference_dist; } /** - * Set maximum distance of sound (the distance where the sound is - * no longer audible. + * Set maximum distance (in meters) of this sound. + * This is the distance where this sound is no longer audible. + * @param dist Maximum distance */ - void set_max_dist( ALfloat dist ); + inline void set_max_dist( float dist ) { + _max_dist = dist; _static_changed = true; + } /** - * Load a sound file into a memory buffer only. + * Get maximum distance (in meters) of this sound. + * This is the distance where this sound is no longer audible. + * @return dist Maximum distance */ - ALvoid* load_file(const char *path, const char *file); + inline float get_max_dist() { return _max_dist; } + + /** + * Get the reference name of this audio sample. + * @return Sample name + */ + inline std::string get_sample_name() const { return _refname; } + + void update_pos_and_orientation(); + +private: + + // Position of the source sound. + SGVec3d _absolute_pos; // absolute position + SGVec3d _relative_pos; // position relative to the base position + SGVec3d _direction; // orientation offset + SGVec3f _velocity; // Velocity of the source sound. + + // The position and orientation of this sound + SGQuatd _orientation; // base orientation + SGVec3f _orivec; // orientation vector for OpenAL + SGVec3d _base_pos; // base position + + SGQuatd _rotation; + + std::string _refname; // name or file path + unsigned char* _data; + + // configuration values + int _format; + size_t _size; + int _freq; + + // Buffers hold sound data. + bool _valid_buffer; + unsigned int _buffer; + + // Sources are points emitting sound. + bool _valid_source; + unsigned int _source; + + // The orientation of this sound (direction and cut-off angles) + float _inner_angle; + float _outer_angle; + float _outer_gain; + + float _pitch; + float _volume; + float _master_volume; + float _reference_dist; + float _max_dist; + bool _loop; + + bool _playing; + bool _changed; + bool _static_changed; + bool _is_file; + + string random_string(); }; diff --git a/simgear/sound/soundmgr_openal.cxx b/simgear/sound/soundmgr_openal.cxx index fb49d247..05651b75 100644 --- a/simgear/sound/soundmgr_openal.cxx +++ b/simgear/sound/soundmgr_openal.cxx @@ -4,8 +4,10 @@ // 2001 // // C++-ified by Curtis Olson, started March 2001. +// Modified for the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt +// Copyright (C) 2009 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -18,8 +20,8 @@ // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ @@ -27,326 +29,631 @@ # include #endif -#include - -#if defined(__APPLE__) -# include -# include +#if defined( __APPLE__ ) +# include #else -# include -# include +# include #endif -#if defined (__APPLE__) -# ifdef __GNUC__ -# if ( __GNUC__ >= 3 ) && ( __GNUC_MINOR__ >= 3 ) -// # include -inline int (isnan)(double r) { return !(r <= 0 || r >= 0); } -# else - // any C++ header file undefines isinf and isnan - // so this should be included before - // the functions are STILL in libm (libSystem on mac os x) -extern "C" int isnan (double); -extern "C" int isinf (double); -# endif -# else -// inline int (isinf)(double r) { return isinf(r); } -// inline int (isnan)(double r) { return isnan(r); } -# endif -#endif - -#if defined (__FreeBSD__) -# if __FreeBSD_version < 500000 - extern "C" { - inline int isnan(double r) { return !(r <= 0 || r >= 0); } - } -# endif -#endif - -#if defined (__CYGWIN__) -#include -#endif - - #include - -#include -#include +#include #include "soundmgr_openal.hxx" -#if defined(__MINGW32__) -#define isnan(x) _isnan(x) -#endif +#include +#include +#include +#include + +extern bool isNaN(float *v); + +#define MAX_SOURCES 128 // // Sound Manager // +int SGSoundMgr::_alut_init = 0; + // constructor -SGSoundMgr::SGSoundMgr() { - - SG_LOG( SG_GENERAL, SG_INFO, "Initializing OpenAL sound manager" ); - - // initialize OpenAL +SGSoundMgr::SGSoundMgr() : + _working(false), + _active(false), + _changed(true), + _volume(0.0), + _device(NULL), + _context(NULL), + _absolute_pos(SGVec3d::zeros()), + _offset_pos(SGVec3d::zeros()), + _base_pos(SGVec3d::zeros()), + _geod_pos(SGGeod::fromCart(SGVec3d::zeros())), + _velocity(SGVec3d::zeros()), + _orientation(SGQuatd::zeros()), + _bad_doppler(false) +{ #if defined(ALUT_API_MAJOR_VERSION) && ALUT_API_MAJOR_VERSION >= 1 - if (!alutInit(NULL, NULL)) - { - ALenum error = alutGetError (); - SG_LOG( SG_GENERAL, SG_ALERT, "Audio initialization failed!" ); - SG_LOG( SG_GENERAL, SG_ALERT, " "+string(alutGetErrorString(error))); - working = false; - context = 0; - return; - } - else - { - working = true; - context = alcGetCurrentContext(); - } -#else - if ( (dev = alcOpenDevice( NULL )) != NULL - && ( context = alcCreateContext( dev, NULL )) != NULL ) { - working = true; - alcMakeContextCurrent( context ); - } else { - working = false; - context = 0; - SG_LOG( SG_GENERAL, SG_ALERT, "Audio initialization failed!" ); - return; + if (_alut_init == 0) { + if ( !alutInitWithoutContext(NULL, NULL) ) { + testForALUTError("alut initialization"); + return; + } } + _alut_init++; #endif - - listener_pos[0] = 0.0; - listener_pos[1] = 0.0; - listener_pos[2] = 0.0; - - listener_vel[0] = 0.0; - listener_vel[1] = 0.0; - listener_vel[2] = 0.0; - - listener_ori[0] = 0.0; - listener_ori[1] = 0.0; - listener_ori[2] = -1.0; - listener_ori[3] = 0.0; - listener_ori[4] = 1.0; - listener_ori[5] = 0.0; - - alListenerf( AL_GAIN, 0.0f ); - alListenerfv( AL_POSITION, listener_pos ); - alListenerfv( AL_VELOCITY, listener_vel ); - alListenerfv( AL_ORIENTATION, listener_ori ); - alGetError(); - if ( alGetError() != AL_NO_ERROR) { - SG_LOG( SG_GENERAL, SG_ALERT, - "Oops AL error after audio initialization!" ); - } - - // exaggerate the ear candy? - alDopplerFactor(1.0); - alDopplerVelocity(340.0); // speed of sound in meters per second. } // destructor SGSoundMgr::~SGSoundMgr() { + stop(); #if defined(ALUT_API_MAJOR_VERSION) && ALUT_API_MAJOR_VERSION >= 1 - alutExit (); -#else - if (context) - alcDestroyContext( context ); + _alut_init--; + if (_alut_init == 0) { + alutExit (); + } #endif } - // initialize the sound manager -void SGSoundMgr::init() { - // - // Remove the samples from the sample manager. - // - samples.clear(); +void SGSoundMgr::init(const char *devname) { + + SG_LOG( SG_GENERAL, SG_INFO, "Initializing OpenAL sound manager" ); + + ALCdevice *device = alcOpenDevice(devname); + if ( testForError(device, "Audio device not available, trying default") ) { + device = alcOpenDevice(NULL); + if (testForError(device, "Default Audio device not available.") ) { + return; + } + } + + ALCcontext *context = alcCreateContext(device, NULL); + if ( testForError(context, "Unable to create a valid context.") ) { + alcCloseDevice (device); + return; + } + + if ( !alcMakeContextCurrent(context) ) { + testForALCError("context initialization"); + alcDestroyContext (context); + alcCloseDevice (device); + return; + } + + if (_context != NULL) + SG_LOG(SG_GENERAL, SG_ALERT, "context is already assigned"); + _context = context; + _working = true; + + _at_up_vec[0] = 0.0; _at_up_vec[1] = 0.0; _at_up_vec[2] = -1.0; + _at_up_vec[3] = 0.0; _at_up_vec[4] = 1.0; _at_up_vec[5] = 0.0; + + alListenerf( AL_GAIN, 0.0f ); + alListenerfv( AL_ORIENTATION, _at_up_vec ); + alListenerfv( AL_POSITION, SGVec3f::zeros().data() ); + alListenerfv( AL_VELOCITY, SGVec3f::zeros().data() ); + + alDopplerFactor(1.0); + alDopplerVelocity(340.3); // speed of sound in meters per second. + + // gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + + // AL_ROLLOFF_FACTOR * (distance - AL_REFERENCE_DISTANCE)); + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + + testForALError("listener initialization"); + + // get a free source one at a time + // if an error is returned no more (hardware) sources are available + for (unsigned int i=0; isecond; + sgrp->activate(); + } + } +} + +// stop the sound manager +void SGSoundMgr::stop() { + + // first stop all sample groups + sample_group_map_iterator sample_grp_current = _sample_groups.begin(); + sample_group_map_iterator sample_grp_end = _sample_groups.end(); + for ( ; sample_grp_current != sample_grp_end; ++sample_grp_current ) { + SGSampleGroup *sgrp = sample_grp_current->second; + sgrp->stop(); + } + + // clear all OpenAL sources + for (unsigned int i=0; i<_free_sources.size(); i++) { + ALuint source = _free_sources[i]; + alDeleteSources( 1 , &source ); + } + _free_sources.clear(); + + // clear any OpenAL buffers before shutting down + buffer_map_iterator buffers_current = _buffers.begin(); + buffer_map_iterator buffers_end = _buffers.end(); + for ( ; buffers_current != buffers_end; ++buffers_current ) { + refUint ref = buffers_current->second; + ALuint buffer = ref.id; + alDeleteBuffers(1, &buffer); + } + _buffers.clear(); + + if (_working) { + _working = false; + _active = false; + _context = alcGetCurrentContext(); + _device = alcGetContextsDevice(_context); + alcDestroyContext(_context); + alcCloseDevice(_device); + _context = NULL; + } +} + +void SGSoundMgr::suspend() { + if (_working) { + sample_group_map_iterator sample_grp_current = _sample_groups.begin(); + sample_group_map_iterator sample_grp_end = _sample_groups.end(); + for ( ; sample_grp_current != sample_grp_end; ++sample_grp_current ) { + SGSampleGroup *sgrp = sample_grp_current->second; + sgrp->stop(); + } + _active = false; + } +} + +void SGSoundMgr::resume() { + if (_working) { + sample_group_map_iterator sample_grp_current = _sample_groups.begin(); + sample_group_map_iterator sample_grp_end = _sample_groups.end(); + for ( ; sample_grp_current != sample_grp_end; ++sample_grp_current ) { + SGSampleGroup *sgrp = sample_grp_current->second; + sgrp->resume(); + } + _active = true; + } +} void SGSoundMgr::bind () { - // no properties + _free_sources.clear(); + _free_sources.reserve( MAX_SOURCES ); + _sources_in_use.clear(); + _sources_in_use.reserve( MAX_SOURCES ); } void SGSoundMgr::unbind () { - // no properties -} + _sample_groups.clear(); + // delete free sources + for (unsigned int i=0; i<_free_sources.size(); i++) { + ALuint source = _free_sources[i]; + alDeleteSources( 1 , &source ); + } + + _free_sources.clear(); + _sources_in_use.clear(); +} // run the audio scheduler void SGSoundMgr::update( double dt ) { -} + if (_active) { + alcSuspendContext(_context); - -void -SGSoundMgr::pause () -{ - if (context) { - alcSuspendContext( context ); - if ( alGetError() != AL_NO_ERROR) { - SG_LOG( SG_GENERAL, SG_ALERT, - "Oops AL error after soundmgr pause()!" ); + if (_changed) { + update_pos_and_orientation(); } + + sample_group_map_iterator sample_grp_current = _sample_groups.begin(); + sample_group_map_iterator sample_grp_end = _sample_groups.end(); + for ( ; sample_grp_current != sample_grp_end; ++sample_grp_current ) { + SGSampleGroup *sgrp = sample_grp_current->second; + sgrp->update(dt); + } + + if (_changed) { +#if 0 +if (isNaN(_at_up_vec)) printf("NaN in listener orientation\n"); +if (isNaN(toVec3f(_absolute_pos).data())) printf("NaN in listener position\n"); +if (isNaN(_velocity.data())) printf("NaN in listener velocity\n"); +#endif + alListenerf( AL_GAIN, _volume ); + alListenerfv( AL_ORIENTATION, _at_up_vec ); + // alListenerfv( AL_POSITION, toVec3f(_absolute_pos).data() ); + + SGQuatd hlOr = SGQuatd::fromLonLat( _geod_pos ); + SGVec3d velocity = SGVec3d::zeros(); + if ( _velocity[0] || _velocity[1] || _velocity[2] ) { + velocity = hlOr.backTransform(_velocity*SG_FEET_TO_METER); + } + + if ( _bad_doppler ) { + velocity *= 100.0f; + } + + alListenerfv( AL_VELOCITY, toVec3f(velocity).data() ); + // alDopplerVelocity(340.3); // TODO: altitude dependent + testForALError("update"); + _changed = false; + } + + alcProcessContext(_context); } } - -void -SGSoundMgr::resume () +// add a sample group, return true if successful +bool SGSoundMgr::add( SGSampleGroup *sgrp, const string& refname ) { - if (context) { - alcProcessContext( context ); - if ( alGetError() != AL_NO_ERROR) { - SG_LOG( SG_GENERAL, SG_ALERT, - "Oops AL error after soundmgr resume()!" ); - } - } -} - - -// add a sound effect, return true if successful -bool SGSoundMgr::add( SGSoundSample *sound, const string& refname ) { - - sample_map_iterator sample_it = samples.find( refname ); - if ( sample_it != samples.end() ) { - // sound already exists + sample_group_map_iterator sample_grp_it = _sample_groups.find( refname ); + if ( sample_grp_it != _sample_groups.end() ) { + // sample group already exists return false; } - samples[refname] = sound; + if (_active) sgrp->activate(); + _sample_groups[refname] = sgrp; return true; } // remove a sound effect, return true if successful -bool SGSoundMgr::remove( const string &refname ) { - - sample_map_iterator sample_it = samples.find( refname ); - if ( sample_it != samples.end() ) { - // first stop the sound from playing (so we don't bomb the - // audio scheduler) - samples.erase( sample_it ); - - // cout << "sndmgr: removed -> " << refname << endl; - return true; - } else { - // cout << "sndmgr: failed remove -> " << refname << endl; +bool SGSoundMgr::remove( const string &refname ) +{ + sample_group_map_iterator sample_grp_it = _sample_groups.find( refname ); + if ( sample_grp_it == _sample_groups.end() ) { + // sample group was not found. return false; } + + _sample_groups.erase( sample_grp_it ); + + return true; } // return true of the specified sound exists in the sound manager system bool SGSoundMgr::exists( const string &refname ) { - sample_map_iterator sample_it = samples.find( refname ); - if ( sample_it != samples.end() ) { - return true; - } else { + sample_group_map_iterator sample_grp_it = _sample_groups.find( refname ); + if ( sample_grp_it == _sample_groups.end() ) { + // sample group was not found. return false; } + + return true; } -// return a pointer to the SGSoundSample if the specified sound exists +// return a pointer to the SGSampleGroup if the specified sound exists // in the sound manager system, otherwise return NULL -SGSoundSample *SGSoundMgr::find( const string &refname ) { - sample_map_iterator sample_it = samples.find( refname ); - if ( sample_it != samples.end() ) { - return sample_it->second; - } else { - return NULL; +SGSampleGroup *SGSoundMgr::find( const string &refname, bool create ) { + sample_group_map_iterator sample_grp_it = _sample_groups.find( refname ); + if ( sample_grp_it == _sample_groups.end() ) { + // sample group was not found. + if (create) { + SGSampleGroup* sgrp = new SGSampleGroup(this, refname); + add( sgrp, refname ); + return sgrp; + } + else + return NULL; } + + return sample_grp_it->second; } -// tell the scheduler to play the indexed sample in a continuous -// loop -bool SGSoundMgr::play_looped( const string &refname ) { - SGSoundSample *sample; +void SGSoundMgr::set_volume( float v ) +{ + _volume = v; + if (_volume > 1.0) _volume = 1.0; + if (_volume < 0.0) _volume = 0.0; + _changed = true; +} - if ( (sample = find( refname )) == NULL ) { - return false; - } else { - sample->play( true ); - return true; +// Get an unused source id +// +// The Sound Manager should keep track of the sources in use, the distance +// of these sources to the listener and the volume (also based on audio cone +// and hence orientation) of the sources. +// +// The Sound Manager is (and should be) the only one knowing about source +// management. Sources further away should be suspendped to free resources for +// newly added sounds close by. +unsigned int SGSoundMgr::request_source() +{ + unsigned int source = NO_SOURCE; + + if (_free_sources.size() > 0) { + source = _free_sources.back(); + _free_sources.pop_back(); + _sources_in_use.push_back(source); + } + else + SG_LOG( SG_GENERAL, SG_INFO, "No more free sources available\n"); + + return source; +} + +// Free up a source id for further use +void SGSoundMgr::release_source( unsigned int source ) +{ + vector::iterator it; + + it = std::find(_sources_in_use.begin(), _sources_in_use.end(), source); + if ( it != _sources_in_use.end() ) { + ALint result; + + alGetSourcei( source, AL_SOURCE_STATE, &result ); + if ( result == AL_PLAYING ) + alSourceStop( source ); + testForALError("release source"); + + alSourcei( source, AL_BUFFER, 0 ); + _free_sources.push_back( source ); + _sources_in_use.erase( it ); } } +unsigned int SGSoundMgr::request_buffer(SGSoundSample *sample) +{ + ALuint buffer = NO_BUFFER; -// tell the scheduler to play the indexed sample once -bool SGSoundMgr::play_once( const string& refname ) { - SGSoundSample *sample; + if ( !sample->is_valid_buffer() ) { + // sample was not yet loaded or removed again + string sample_name = sample->get_sample_name(); + void *sample_data = NULL; - if ( (sample = find( refname )) == NULL ) { - return false; - } else { - sample->play( false ); - return true; + // see if the sample name is already cached + buffer_map_iterator buffer_it = _buffers.find( sample_name ); + if ( buffer_it != _buffers.end() ) { + buffer_it->second.refctr++; + buffer = buffer_it->second.id; + sample->set_buffer( buffer ); + return buffer; + } + + // sample name was not found in the buffer cache. + if ( sample->is_file() ) { + int freq, format; + size_t size; + bool res; + + res = load(sample_name, &sample_data, &format, &size, &freq); + if (res == false) return buffer; + + sample->set_frequency( freq ); + sample->set_format( format ); + sample->set_size( size ); + } + else + sample_data = sample->get_data(); + + // create an OpenAL buffer handle + alGenBuffers(1, &buffer); + if ( !testForALError("generate buffer") ) { + // Copy data to the internal OpenAL buffer + + ALenum format = sample->get_format(); + ALsizei size = sample->get_size(); + ALsizei freq = sample->get_frequency(); + alBufferData( buffer, format, sample_data, size, freq ); + + if ( sample->is_file() ) free(sample_data); + + if ( !testForALError("buffer add data") ) { + sample->set_buffer(buffer); + _buffers[sample_name] = refUint(buffer); + } + } } + else { + buffer = sample->get_buffer(); } - -// return true of the specified sound is currently being played -bool SGSoundMgr::is_playing( const string& refname ) { - SGSoundSample *sample; - - if ( (sample = find( refname )) == NULL ) { - return false; - } else { - return ( sample->is_playing() ); - } + return buffer; } - -// immediate stop playing the sound -bool SGSoundMgr::stop( const string& refname ) { - SGSoundSample *sample; - - if ( (sample = find( refname )) == NULL ) { - return false; - } else { - sample->stop(); - return true; - } -} - - -// set source position of all managed sounds -void SGSoundMgr::set_source_pos_all( ALfloat *pos ) { - if ( isnan(pos[0]) || isnan(pos[1]) || isnan(pos[2]) ) { - // bail if a bad position is passed in +void SGSoundMgr::release_buffer(SGSoundSample *sample) +{ + string sample_name = sample->get_sample_name(); + buffer_map_iterator buffer_it = _buffers.find( sample_name ); + if ( buffer_it == _buffers.end() ) { + // buffer was not found return; } - sample_map_iterator sample_current = samples.begin(); - sample_map_iterator sample_end = samples.end(); - for ( ; sample_current != sample_end; ++sample_current ) { - SGSoundSample *sample = sample_current->second; - sample->set_source_pos( pos ); + sample->no_valid_buffer(); + buffer_it->second.refctr--; + if (buffer_it->second.refctr == 0) { + ALuint buffer = buffer_it->second.id; + alDeleteBuffers(1, &buffer); + _buffers.erase( buffer_it ); + testForALError("release buffer"); } } +void SGSoundMgr::update_pos_and_orientation() { + /** + * Description: ORIENTATION is a pair of 3-tuples representing the + * 'at' direction vector and 'up' direction of the Object in + * Cartesian space. AL expects two vectors that are orthogonal to + * each other. These vectors are not expected to be normalized. If + * one or more vectors have zero length, implementation behavior + * is undefined. If the two vectors are linearly dependent, + * behavior is undefined. + * + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right. + */ + SGVec3d sgv_at = _orientation.backTransform(-SGVec3d::e3()); + SGVec3d sgv_up = _orientation.backTransform(SGVec3d::e2()); + _at_up_vec[0] = sgv_at[0]; + _at_up_vec[1] = sgv_at[1]; + _at_up_vec[2] = sgv_at[2]; + _at_up_vec[3] = sgv_up[0]; + _at_up_vec[4] = sgv_up[1]; + _at_up_vec[5] = sgv_up[2]; -// set source velocity of all managed sounds -void SGSoundMgr::set_source_vel_all( ALfloat *vel ) { - if ( isnan(vel[0]) || isnan(vel[1]) || isnan(vel[2]) ) { - // bail if a bad velocity is passed in - return; - } - - sample_map_iterator sample_current = samples.begin(); - sample_map_iterator sample_end = samples.end(); - for ( ; sample_current != sample_end; ++sample_current ) { - SGSoundSample *sample = sample_current->second; - sample->set_source_vel( vel, listener_vel ); - } + // static const SGQuatd q(-0.5, -0.5, 0.5, 0.5); + // SGQuatd hlOr = SGQuatd::fromLonLat(SGGeod::fromCart(_base_pos)); + // SGQuatd ec2body = hlOr*_orientation; + _absolute_pos = _base_pos; // + ec2body.backTransform( _offset_pos ); +} + +bool SGSoundMgr::load(string &samplepath, void **dbuf, int *fmt, + size_t *sz, int *frq ) +{ + if ( !_working ) return false; + + ALenum format; + ALsizei size; + ALsizei freq; + ALvoid *data; + +#if defined(ALUT_API_MAJOR_VERSION) && ALUT_API_MAJOR_VERSION >= 1 + ALfloat freqf; + data = alutLoadMemoryFromFile(samplepath.c_str(), &format, &size, &freqf ); + freq = (ALsizei)freqf; + if (data == NULL) { + int error = alutGetError(); + string msg = "Failed to load wav file: "; + msg.append(alutGetErrorString(error)); + throw sg_io_exception(msg.c_str(), sg_location(samplepath)); + return false; + } + +#else + ALbyte *fname = (ALbyte *)samplepath.c_str(); +# if defined (__APPLE__) + alutLoadWAVFile( fname, &format, &data, &size, &freq ); +# else + ALboolean loop; + alutLoadWAVFile( fname, &format, &data, &size, &freq, &loop ); +# endif + ALenum error = alGetError(); + if ( error != AL_NO_ERROR ) { + string msg = "Failed to load wav file: "; + msg.append(alGetString(error)); + throw sg_io_exception(msg.c_str(), sg_location(samplepath)); + return false; + } +#endif + + *dbuf = (void *)data; + *fmt = (int)format; + *sz = (size_t)size; + *frq = (int)freq; + + return true; +} + +vector SGSoundMgr::get_available_devices() +{ + vector devices; + const ALCchar *s; + + if (alcIsExtensionPresent(NULL, "ALC_enumerate_all_EXT") == AL_TRUE) { + s = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + } else { + s = alcGetString(NULL, ALC_DEVICE_SPECIFIER); + } + + if (s) { + ALCchar *nptr, *ptr = (ALCchar *)s; + + nptr = ptr; + while (*(nptr += strlen(ptr)+1) != 0) + { + devices.push_back(ptr); + ptr = nptr; + } + devices.push_back(ptr); + } + + return devices; +} + + +bool SGSoundMgr::testForError(void *p, string s) +{ + if (p == NULL) { + SG_LOG( SG_GENERAL, SG_ALERT, "Error: " << s); + return true; + } + return false; +} + + +bool SGSoundMgr::testForALError(string s) +{ + ALenum error = alGetError(); + if (error != AL_NO_ERROR) { + SG_LOG( SG_GENERAL, SG_ALERT, "AL Error (sound manager): " + << alGetString(error) << " at " << s); + return true; + } + return false; +} + +bool SGSoundMgr::testForALCError(string s) +{ + ALCenum error; + error = alcGetError(_device); + if (error != ALC_NO_ERROR) { + SG_LOG( SG_GENERAL, SG_ALERT, "ALC Error (sound manager): " + << alcGetString(_device, error) << " at " + << s); + return true; + } + return false; +} + +bool SGSoundMgr::testForALUTError(string s) +{ +#if defined(ALUT_API_MAJOR_VERSION) && ALUT_API_MAJOR_VERSION >= 1 + ALenum error; + error = alutGetError (); + if (error != ALUT_ERROR_NO_ERROR) { + SG_LOG( SG_GENERAL, SG_ALERT, "ALUT Error (sound manager): " + << alutGetErrorString(error) << " at " + << s); + return true; + } +#endif + return false; } diff --git a/simgear/sound/soundmgr_openal.hxx b/simgear/sound/soundmgr_openal.hxx index 59cb24e4..2aa78bc5 100644 --- a/simgear/sound/soundmgr_openal.hxx +++ b/simgear/sound/soundmgr_openal.hxx @@ -4,8 +4,10 @@ // 2001 // // C++-ified by Curtis Olson, started March 2001. +// Modified for the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt +// Copyright (C) 2009 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -18,8 +20,8 @@ // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // $Id$ @@ -37,210 +39,296 @@ # error This library requires C++ #endif -#include - #include +#include #include -#if defined( __APPLE__ ) +#if defined(__APPLE__) +# define AL_ILLEGAL_ENUM AL_INVALID_ENUM +# define AL_ILLEGAL_COMMAND AL_INVALID_OPERATION # include # include +# include +#elif defined(OPENALSDK) +# include +# include +# include #else # include # include +# include #endif +#ifndef ALC_ALL_DEVICES_SPECIFIER +# define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +#include +#include +#include + +#include "sample_group.hxx" #include "sample_openal.hxx" -using std::map; using std::string; +struct refUint { + unsigned int refctr; + ALuint id; -typedef map < string, SGSharedPtr > sample_map; -typedef sample_map::iterator sample_map_iterator; -typedef sample_map::const_iterator const_sample_map_iterator; + refUint() { refctr = 0; id = (ALuint)-1; }; + refUint(ALuint i) { refctr = 1; id = i; }; + ~refUint() {}; +}; +typedef std::map < string, refUint > buffer_map; +typedef buffer_map::iterator buffer_map_iterator; +typedef buffer_map::const_iterator const_buffer_map_iterator; + +typedef std::map < string, SGSharedPtr > sample_group_map; +typedef sample_group_map::iterator sample_group_map_iterator; +typedef sample_group_map::const_iterator const_sample_group_map_iterator; /** - * Manage a collection of SGSoundSample instances + * Manage a collection of SGSampleGroup instances */ -class SGSoundMgr +class SGSoundMgr : public SGSubsystem { - - ALCdevice *dev; - ALCcontext *context; - - // Position of the listener. - ALfloat listener_pos[3]; - - // Velocity of the listener. - ALfloat listener_vel[3]; - - // Orientation of the listener. (first 3 elements are "at", second - // 3 are "up") - ALfloat listener_ori[6]; - - sample_map samples; - - bool working; - double safety; - public: SGSoundMgr(); ~SGSoundMgr(); - - /** - * (re) initialize the sound manager. - */ - void init(); - - - /** - * Bind properties for the sound manager. - */ + void init(const char *devname = NULL); void bind(); - - - /** - * Unbind properties for the sound manager. - */ void unbind(); - - - /** - * Run the audio scheduler. - */ void update(double dt); - - - /** - * Pause all sounds. - */ - void pause(); - - - /** - * Resume all sounds. - */ + + void suspend(); void resume(); + void stop(); + inline void reinit() { stop(); init(); } /** - * is audio working? + * Test is the sound manager is in a working condition. + * @return true is the sound manager is working */ - inline bool is_working() const { return working; } + inline bool is_working() const { return _working; } /** - * reinitialize the sound manager + * Set the sound manager to a working condition. */ - inline void reinit() { init(); } + void activate(); /** - * add a sound effect, return true if successful + * Test is the sound manager is in an active and working condition. + * @return true is the sound manager is active */ - bool add( SGSoundSample *sound, const string& refname); + inline bool is_active() const { return _active; } + + /** + * Register a sample group to the sound manager. + * @para sgrp Pointer to a sample group to add + * @param refname Reference name of the sample group + * @return true if successful, false otherwise + */ + bool add( SGSampleGroup *sgrp, const string& refname ); /** - * remove a sound effect, return true if successful + * Remove a sample group from the sound manager. + * @param refname Reference name of the sample group to remove + * @return true if successful, false otherwise */ bool remove( const string& refname ); /** - * return true of the specified sound exists in the sound manager system + * Test if a specified sample group is registered at the sound manager + * @param refname Reference name of the sample group test for + * @return true if the specified sample group exists */ bool exists( const string& refname ); /** - * return a pointer to the SGSoundSample if the specified sound - * exists in the sound manager system, otherwise return NULL + * Find a specified sample group in the sound manager + * @param refname Reference name of the sample group to find + * @return A pointer to the SGSampleGroup */ - SGSoundSample *find( const string& refname ); + SGSampleGroup *find( const string& refname, bool create = false ); /** - * tell the scheduler to play the indexed sample in a continuous - * loop + * Set the Cartesian position of the sound manager. + * @param pos OpenAL listener position */ - bool play_looped( const string& refname ); + void set_position( const SGVec3d& pos, const SGGeod& pos_geod ) { + _base_pos = pos; _geod_pos = pos_geod; _changed = true; + } - /** - * tell the scheduler to play the indexed sample once - */ - bool play_once( const string& refname ); - - /** - * return true of the specified sound is currently being played - */ - bool is_playing( const string& refname ); - - /** - * immediate stop playing the sound - */ - bool stop( const string& refname ); - - /** - * set overall volume for the application. - * @param vol 1.0 is default, must be greater than 0 - */ - inline void set_volume( const ALfloat vol ) { - if ( vol > 0.0 ) { - alListenerf( AL_GAIN, vol ); - } + void set_position_offset( const SGVec3d& pos ) { + _offset_pos = pos; _changed = true; } /** - * set the position of the listener (in opengl coordinates) + * Get the position of the sound manager. + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right + * @return OpenAL listener position */ - inline void set_listener_pos( ALfloat *pos ) { - listener_pos[0] = pos[0]; - listener_pos[1] = pos[1]; - listener_pos[2] = pos[2]; - alListenerfv( AL_POSITION, listener_pos ); + SGVec3d& get_position() { return _absolute_pos; } + + /** + * Set the velocity vector (in meters per second) of the sound manager + * This is the horizontal local frame; x=north, y=east, z=down + * @param Velocity vector + */ + void set_velocity( const SGVec3d& vel ) { + _velocity = vel; _changed = true; } /** - * set the velocity of the listener (in opengl coordinates) + * Get the velocity vector of the sound manager + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right. + * @return Velocity vector of the OpenAL listener */ - inline void set_listener_vel( ALfloat *vel ) { - listener_vel[0] = vel[0]; - listener_vel[1] = vel[1]; - listener_vel[2] = vel[2]; -#ifdef USE_OPEN_AL_DOPPLER - alListenerfv( AL_VELOCITY, listener_vel ); -#endif + inline SGVec3f get_velocity() { return toVec3f(_velocity); } + + /** + * Set the orientation of the sound manager + * @param ori Quaternation containing the orientation information + */ + void set_orientation( const SGQuatd& ori ) { + _orientation = ori; _changed = true; } /** - * set the orientation of the listener (in opengl coordinates) - * - * Description: ORIENTATION is a pair of 3-tuples representing the - * 'at' direction vector and 'up' direction of the Object in - * Cartesian space. AL expects two vectors that are orthogonal to - * each other. These vectors are not expected to be normalized. If - * one or more vectors have zero length, implementation behavior - * is undefined. If the two vectors are linearly dependent, - * behavior is undefined. + * Get the orientation of the sound manager + * @return Quaternation containing the orientation information */ - inline void set_listener_orientation( ALfloat *ori ) { - listener_ori[0] = ori[0]; - listener_ori[1] = ori[1]; - listener_ori[2] = ori[2]; - listener_ori[3] = ori[3]; - listener_ori[4] = ori[4]; - listener_ori[5] = ori[5]; - alListenerfv( AL_ORIENTATION, listener_ori ); + inline const SGQuatd& get_orientation() { return _orientation; } + + /** + * Get the direction vector of the sound manager + * This is in the same coordinate system as OpenGL; y=up, z=back, x=right. + * @return Look-at direction of the OpenAL listener + */ + SGVec3f get_direction() { + return SGVec3f(_at_up_vec[0], _at_up_vec[1], _at_up_vec[2]); } - /** - * set the positions of all managed sound sources - */ - void set_source_pos_all( ALfloat *pos ); + enum { + NO_SOURCE = (unsigned int)-1, + NO_BUFFER = (unsigned int)-1 + }; /** - * set the velocities of all managed sound sources + * Set the master volume. + * @param vol Volume (must be between 0.0 and 1.0) */ - void set_source_vel_all( ALfloat *pos ); + void set_volume( float vol ); + + /** + * Get the master volume. + * @return Volume (must be between 0.0 and 1.0) + */ + inline float get_volume() { return _volume; } + + /** + * Get a free OpenAL source-id + * @return NO_SOURCE if no source is available + */ + unsigned int request_source(); + + /** + * Free an OpenAL source-id for future use + * @param source OpenAL source-id to free + */ + void release_source( unsigned int source ); + + /** + * Get a free OpenAL buffer-id + * The buffer-id will be asigned to the sample by calling this function. + * @param sample Pointer to an audio sample to assign the buffer-id to + * @return NO_BUFFER if loading of the buffer failed. + */ + unsigned int request_buffer(SGSoundSample *sample); + + /** + * Free an OpenAL buffer-id for this sample + * @param sample Pointer to an audio sample for which to free the buffer + */ + void release_buffer( SGSoundSample *sample ); + + /** + * Test if the position of the sound manager has changed. + * The value will be set to false upon the next call to update_late() + * @return true if the position has changed + */ + inline bool has_changed() { return _changed; } + + /** + * Some implementations seem to need the velocity miltyplied by a + * factor of 100 to make them distinct. I've not found if this is + * a problem in the implementation or in out code. Until then + * this function is used to detect the problematic implementations. + */ + inline bool bad_doppler_effect() { return _bad_doppler; } + + /** + * Load a sample file and return it's configuration and data. + * @param samplepath Path to the file to load + * @param data Pointer to a variable that points to the allocated data + * @param format Pointer to a vairable that gets the OpenAL format + * @param size Pointer to a vairable that gets the sample size in bytes + * @param freq Pointer to a vairable that gets the sample frequency in Herz + * @return true if succesful, false on error + */ + bool load(string &samplepath, void **data, int *format, + size_t *size, int *freq ); + + /** + * Get a list of available playback devices. + */ + vector get_available_devices(); + +private: + static int _alut_init; + + bool _working; + bool _active; + bool _changed; + float _volume; + + ALCdevice *_device; + ALCcontext *_context; + + // Position of the listener. + SGVec3d _absolute_pos; + SGVec3d _offset_pos; + SGVec3d _base_pos; + SGGeod _geod_pos; + + // Velocity of the listener. + SGVec3d _velocity; + + // Orientation of the listener. + // first 3 elements are "at" vector, second 3 are "up" vector + SGQuatd _orientation; + ALfloat _at_up_vec[6]; + + sample_group_map _sample_groups; + buffer_map _buffers; + + vector _free_sources; + vector _sources_in_use; + + bool _bad_doppler; + + bool testForALError(string s); + bool testForALCError(string s); + bool testForALUTError(string s); + bool testForError(void *p, string s); + + void update_pos_and_orientation(); + void update_sample_config( SGSampleGroup *sound ); }; diff --git a/simgear/sound/xmlsound.cxx b/simgear/sound/xmlsound.cxx index 8dd8df93..479e3123 100644 --- a/simgear/sound/xmlsound.cxx +++ b/simgear/sound/xmlsound.cxx @@ -41,8 +41,8 @@ static double _snd_inv(double v) { return (v == 0) ? 1e99 : 1/v; } static double _snd_abs(double v) { return (v >= 0) ? v : -v; } static double _snd_sqrt(double v) { return sqrt(fabs(v)); } -static double _snd_log10(double v) { return log10(fabs(v)); } -static double _snd_log(double v) { return log(fabs(v)); } +static double _snd_log10(double v) { return log10(fabs(v)+1e-9); } +static double _snd_log(double v) { return log(fabs(v)+1e-9); } // static double _snd_sqr(double v) { return v*v; } // static double _snd_pow3(double v) { return v*v*v; } @@ -50,14 +50,11 @@ static const struct { const char *name; double (*fn)(double); } __sound_fn[] = { -// {"lin", _snd_lin}, {"inv", _snd_inv}, {"abs", _snd_abs}, {"sqrt", _snd_sqrt}, {"log", _snd_log10}, {"ln", _snd_log}, -// {"sqr", _snd_sqr}, -// {"pow3", _snd_pow3}, {"", NULL} }; @@ -84,7 +81,8 @@ SGXmlSound::~SGXmlSound() } void -SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, SGSoundMgr *sndmgr, +SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, + SGSampleGroup *sgrp, SGSampleGroup *avionics, const string &path) { @@ -92,10 +90,6 @@ SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, SGSoundMgr *sndmgr, // set global sound properties // - if (sndmgr->is_working() == false) { - return; - } - _name = node->getStringValue("name", ""); SG_LOG(SG_GENERAL, SG_DEBUG, "Loading sound information for: " << _name ); @@ -108,11 +102,13 @@ SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, SGSoundMgr *sndmgr, } else { _mode = SGXmlSound::ONCE; - - if ( strcmp(mode_str, "") ) - SG_LOG(SG_GENERAL,SG_INFO, "Unknown sound mode for '" << _name << "', default to 'once'"); } + bool is_avionics = false; + const char *type_str = node->getStringValue("type", "fx"); + if ( !strcmp(type_str, "avionics") ) + is_avionics = true; + _property = root->getNode(node->getStringValue("property", ""), true); SGPropertyNode *condition = node->getChild("condition"); if (condition != NULL) @@ -178,9 +174,10 @@ SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, SGSoundMgr *sndmgr, } - float reference_dist = node->getDoubleValue("reference-dist", 500.0); - float max_dist = node->getDoubleValue("max-dist", 3000.0); - + // rule of thumb: make reference distance a 100th of the maximum distance. + float reference_dist = node->getDoubleValue("reference-dist", 60.0); + float max_dist = node->getDoubleValue("max-dist", 6000.0); + // // set pitch properties // @@ -236,58 +233,48 @@ SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, SGSoundMgr *sndmgr, // // Relative position // - sgVec3 offset_pos; - sgSetVec3( offset_pos, 0.0, 0.0, 0.0 ); - SGPropertyNode_ptr pos = node->getChild("position"); - if ( pos != NULL ) { - offset_pos[0] = pos->getDoubleValue("x", 0.0); - offset_pos[1] = -pos->getDoubleValue("y", 0.0); - offset_pos[2] = pos->getDoubleValue("z", 0.0); + SGVec3f offset_pos = SGVec3f::zeros(); + SGPropertyNode_ptr prop = node->getChild("position"); + if ( prop != NULL ) { + offset_pos[0] = -prop->getDoubleValue("x", 0.0); + offset_pos[1] = -prop->getDoubleValue("y", 0.0); + offset_pos[2] = -prop->getDoubleValue("z", 0.0); } // // Orientation // - sgVec3 dir; - float inner, outer, outer_gain; - sgSetVec3( dir, 0.0, 0.0, 0.0 ); - inner = outer = 360.0; - outer_gain = 0.0; - pos = node->getChild("orientation"); - if ( pos != NULL ) { - dir[0] = pos->getDoubleValue("x", 0.0); - dir[1] = -pos->getDoubleValue("y", 0.0); - dir[2] = pos->getDoubleValue("z", 0.0); - inner = pos->getDoubleValue("inner-angle", 360.0); - outer = pos->getDoubleValue("outer-angle", 360.0); - outer_gain = pos->getDoubleValue("outer-gain", 0.0); + SGVec3f dir = SGVec3f::zeros(); + float inner = 360.0; + float outer = 360.0; + float outer_gain = 0.0; + prop = node->getChild("orientation"); + if ( prop != NULL ) { + dir = SGVec3f(-prop->getFloatValue("x", 0.0), + -prop->getFloatValue("y", 0.0), + -prop->getFloatValue("z", 0.0)); + inner = prop->getFloatValue("inner-angle", 360.0); + outer = prop->getFloatValue("outer-angle", 360.0); + outer_gain = prop->getFloatValue("outer-gain", 0.0); } - + // // Initialize the sample // - _mgr = sndmgr; - if ( (_sample = _mgr->find(_name)) == NULL ) { - // FIXME: Does it make sense to overwrite a previous entry's - // configuration just because a new entry has the same name? - // Note that we can't match on identical "path" because we the - // new entry could be at a different location with different - // configuration so we need a new sample which creates a new - // "alSource". The semantics of what is going on here seems - // confused and needs to be thought through more carefully. - _sample = new SGSoundSample( path.c_str(), - node->getStringValue("path", ""), - false ); - - _mgr->add( _sample, _name ); + if (is_avionics) { + _sgrp = avionics; + } else { + _sgrp = sgrp; } - - _sample->set_offset_pos( offset_pos ); - _sample->set_orientation(dir, inner, outer, outer_gain); - _sample->set_volume(v); + _sample = new SGSoundSample( path.c_str(), node->getStringValue("path", "")); + _sample->set_relative_position( offset_pos ); + _sample->set_direction( dir ); + _sample->set_audio_cone( inner, outer, outer_gain ); _sample->set_reference_dist( reference_dist ); _sample->set_max_dist( max_dist ); - _sample->set_pitch(p); + _sample->set_volume( v ); + _sample->set_pitch( p ); + _sgrp->add( _sample, _name ); } void @@ -316,7 +303,8 @@ SGXmlSound::update (double dt) ) ) { - if ((_mode != SGXmlSound::IN_TRANSIT) || (_stopping > MAX_TRANSIT_TIME)) { + if ((_mode != SGXmlSound::IN_TRANSIT) || (_stopping > MAX_TRANSIT_TIME)) + { if (_sample->is_playing()) { SG_LOG(SG_GENERAL, SG_DEBUG, "Stopping audio after " << _dt_play << " sec: " << _name ); diff --git a/simgear/sound/xmlsound.hxx b/simgear/sound/xmlsound.hxx index d161bbca..141f5eef 100644 --- a/simgear/sound/xmlsound.hxx +++ b/simgear/sound/xmlsound.hxx @@ -38,8 +38,8 @@ #include #include +#include "sample_group.hxx" #include "sample_openal.hxx" -#include "soundmgr_openal.hxx" static const double MAX_TRANSIT_TIME = 0.1; // 100 ms. @@ -99,11 +99,12 @@ public: * @param root The root node of the programs property tree. * @param child A pointer to the location of the current event as defined * in the configuration file. - * @param sndmgr A pointer to a pre-initialized sound manager class. + * @param sgrp A pointer to a pre-initialized sample group class. + * @param avionics A pointer to the pre-initialized avionics sample group. * @param path The path where the audio files remain. */ - virtual void init (SGPropertyNode *, SGPropertyNode *, SGSoundMgr *, - const string &); + virtual void init (SGPropertyNode *, SGPropertyNode *, SGSampleGroup *, + SGSampleGroup *, const string &); /** * Check whether an event has happened and if action has to be taken. @@ -135,7 +136,7 @@ protected: private: - SGSoundMgr * _mgr; + SGSampleGroup * _sgrp; SGSharedPtr _sample; SGSharedPtr _condition; diff --git a/simgear/structure/SGAtomic.cxx b/simgear/structure/SGAtomic.cxx index ee219d84..e7dd76c2 100644 --- a/simgear/structure/SGAtomic.cxx +++ b/simgear/structure/SGAtomic.cxx @@ -20,7 +20,7 @@ #include "SGAtomic.hxx" -#if defined(SGATOMIC_USE_GCC4_BUILTINS) && defined(__i386__) +#if defined(SGATOMIC_USE_GCC4_BUILTINS) && defined (__i386__) // Usually the apropriate functions are inlined by gcc. // But if gcc is called with something aequivalent to -march=i386,