1 // sample_group.cxx -- Manage a group of samples relative to a base position
3 // Written for the new SoundSystem by Erik Hofman, October 2009
5 // Copyright (C) 2009 Erik Hofman <erik@ehofman.com>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 # include <simgear_config.h>
28 #include <simgear/compiler.h>
29 #include <simgear/sg_inlines.h>
30 #include <simgear/debug/logstream.hxx>
32 #include "soundmgr_openal.hxx"
33 #include "soundmgr_openal_private.hxx"
34 #include "sample_group.hxx"
39 bool isNaN(float *v) {
40 return (isnan(v[0]) || isnan(v[1]) || isnan(v[2]));
43 SGSampleGroup::SGSampleGroup () :
50 _tied_to_listener(false),
51 _velocity(SGVec3d::zeros()),
52 _orientation(SGQuatd::zeros())
57 SGSampleGroup::SGSampleGroup ( SGSoundMgr *smgr, const string &refname ) :
64 _tied_to_listener(false),
65 _velocity(SGVec3d::zeros()),
66 _orientation(SGQuatd::zeros())
68 _smgr->add(this, refname);
72 SGSampleGroup::~SGSampleGroup ()
79 static bool is_sample_stopped(SGSoundSample *sample)
82 assert(sample->is_valid_source());
83 unsigned int source = sample->get_source();
85 alGetSourcei( source, AL_SOURCE_STATE, &result );
86 return (result == AL_STOPPED);
92 void SGSampleGroup::cleanup_removed_samples()
94 // Delete any OpenAL buffers that might still be in use.
95 unsigned int size = _removed_samples.size();
96 for (unsigned int i=0; i<size; ) {
97 SGSoundSample *sample = _removed_samples[i];
98 bool stopped = is_sample_stopped(sample);
100 if ( sample->is_valid_source() ) {
101 int source = sample->get_source();
103 if ( sample->is_looping() && !stopped) {
105 alSourceStop( source );
107 stopped = is_sample_stopped(sample);
111 sample->no_valid_source();
112 _smgr->release_source( source );
118 if (( !sample->is_queue() )&&
119 (sample->is_valid_buffer()))
121 _smgr->release_buffer(sample);
123 _removed_samples.erase( _removed_samples.begin()+i );
131 void SGSampleGroup::start_playing_sample(SGSoundSample *sample)
135 // a request to start playing a sound has been filed.
137 ALuint source = _smgr->request_source();
138 if (alIsSource(source) == AL_FALSE ) {
142 sample->set_source( source );
143 update_sample_config( sample );
144 ALboolean looping = sample->is_looping() ? AL_TRUE : AL_FALSE;
146 if ( !sample->is_queue() )
148 ALuint buffer = _smgr->request_buffer(sample);
149 if (buffer == SGSoundMgr::FAILED_BUFFER ||
150 buffer == SGSoundMgr::NO_BUFFER)
152 _smgr->release_source(source);
156 // start playing the sample
157 buffer = sample->get_buffer();
158 if ( alIsBuffer(buffer) == AL_TRUE )
160 alSourcei( source, AL_BUFFER, buffer );
161 testForALError("assign buffer to source");
163 SG_LOG( SG_SOUND, SG_ALERT, "No such buffer!");
166 alSourcef( source, AL_ROLLOFF_FACTOR, 0.3 );
167 alSourcei( source, AL_LOOPING, looping );
168 alSourcei( source, AL_SOURCE_RELATIVE, AL_FALSE );
169 alSourcePlay( source );
170 testForALError("sample play");
174 void SGSampleGroup::check_playing_sample(SGSoundSample *sample)
176 // check if the sound has stopped by itself
178 if (is_sample_stopped(sample)) {
179 // sample is stopped because it wasn't looping
181 sample->no_valid_source();
182 _smgr->release_source( sample->get_source() );
183 _smgr->release_buffer( sample );
184 remove( sample->get_sample_name() );
185 } else if ( sample->has_changed() ) {
186 if ( !sample->is_playing() ) {
187 // a request to stop playing the sound has been filed.
189 sample->no_valid_source();
190 _smgr->release_source( sample->get_source() );
191 } else if ( _smgr->has_changed() ) {
192 update_sample_config( sample );
197 void SGSampleGroup::update( double dt ) {
199 if ( !_active || _pause ) return;
201 testForALError("start of update!!\n");
203 cleanup_removed_samples();
205 // Update the position and orientation information for all samples.
206 if ( _changed || _smgr->has_changed() ) {
207 update_pos_and_orientation();
211 sample_map_iterator sample_current = _samples.begin();
212 sample_map_iterator sample_end = _samples.end();
213 for ( ; sample_current != sample_end; ++sample_current ) {
214 SGSoundSample *sample = sample_current->second;
216 if ( !sample->is_valid_source() && sample->is_playing() && !sample->test_out_of_range()) {
217 start_playing_sample(sample);
219 } else if ( sample->is_valid_source() ) {
220 check_playing_sample(sample);
222 testForALError("update");
226 // add a sound effect, return true if successful
227 bool SGSampleGroup::add( SGSharedPtr<SGSoundSample> sound, const string& refname ) {
229 sample_map_iterator sample_it = _samples.find( refname );
230 if ( sample_it != _samples.end() ) {
231 // sample name already exists
235 _samples[refname] = sound;
240 // remove a sound effect, return true if successful
241 bool SGSampleGroup::remove( const string &refname ) {
243 sample_map_iterator sample_it = _samples.find( refname );
244 if ( sample_it == _samples.end() ) {
245 // sample was not found
249 if ( sample_it->second->is_valid_buffer() )
250 _removed_samples.push_back( sample_it->second );
252 _samples.erase( sample_it );
258 // return true of the specified sound exists in the sound manager system
259 bool SGSampleGroup::exists( const string &refname ) {
260 sample_map_iterator sample_it = _samples.find( refname );
261 if ( sample_it == _samples.end() ) {
262 // sample was not found
270 // return a pointer to the SGSoundSample if the specified sound exists
271 // in the sound manager system, otherwise return NULL
272 SGSoundSample *SGSampleGroup::find( const string &refname ) {
273 sample_map_iterator sample_it = _samples.find( refname );
274 if ( sample_it == _samples.end() ) {
275 // sample was not found
279 return sample_it->second;
284 SGSampleGroup::stop ()
287 sample_map_iterator sample_current = _samples.begin();
288 sample_map_iterator sample_end = _samples.end();
289 for ( ; sample_current != sample_end; ++sample_current ) {
290 SGSoundSample *sample = sample_current->second;
292 if ( sample->is_valid_source() ) {
294 ALint source = sample->get_source();
295 if ( sample->is_playing() ) {
296 alSourceStop( source );
297 testForALError("stop");
299 _smgr->release_source( source );
301 sample->no_valid_source();
304 if ( sample->is_valid_buffer() ) {
305 _smgr->release_buffer( sample );
306 sample->no_valid_buffer();
311 // stop playing all associated samples
313 SGSampleGroup::suspend ()
315 if (_active && _pause == false) {
317 sample_map_iterator sample_current = _samples.begin();
318 sample_map_iterator sample_end = _samples.end();
319 for ( ; sample_current != sample_end; ++sample_current ) {
321 SGSoundSample *sample = sample_current->second;
322 if ( sample->is_valid_source() && sample->is_playing() ) {
323 alSourcePause( sample->get_source() );
327 testForALError("suspend");
331 // resume playing all associated samples
333 SGSampleGroup::resume ()
335 if (_active && _pause == true) {
337 sample_map_iterator sample_current = _samples.begin();
338 sample_map_iterator sample_end = _samples.end();
339 for ( ; sample_current != sample_end; ++sample_current ) {
340 SGSoundSample *sample = sample_current->second;
341 if ( sample->is_valid_source() && sample->is_playing() ) {
342 alSourcePlay( sample->get_source() );
345 testForALError("resume");
352 // tell the scheduler to play the indexed sample in a continuous loop
353 bool SGSampleGroup::play( const string &refname, bool looping = false ) {
354 SGSoundSample *sample = find( refname );
356 if ( sample == NULL ) {
360 sample->play( looping );
365 // return true of the specified sound is currently being played
366 bool SGSampleGroup::is_playing( const string& refname ) {
367 SGSoundSample *sample = find( refname );
369 if ( sample == NULL ) {
373 return ( sample->is_playing() ) ? true : false;
376 // immediate stop playing the sound
377 bool SGSampleGroup::stop( const string& refname ) {
378 SGSoundSample *sample = find( refname );
380 if ( sample == NULL ) {
388 void SGSampleGroup::set_volume( float vol )
390 if (vol > _volume*1.01 || vol < _volume*0.99) {
392 SG_CLAMP_RANGE(_volume, 0.0f, 1.0f);
397 // set the source position and orientation of all managed sounds
398 void SGSampleGroup::update_pos_and_orientation() {
400 SGVec3d position = SGVec3d::fromGeod(_base_pos) - _smgr->get_position();
401 SGQuatd hlOr = SGQuatd::fromLonLat(_base_pos);
402 SGQuatd ec2body = hlOr*_orientation;
404 SGVec3f velocity = SGVec3f::zeros();
405 if ( _velocity[0] || _velocity[1] || _velocity[2] ) {
406 velocity = toVec3f( hlOr.backTransform(_velocity*SG_FEET_TO_METER) );
409 sample_map_iterator sample_current = _samples.begin();
410 sample_map_iterator sample_end = _samples.end();
411 for ( ; sample_current != sample_end; ++sample_current ) {
412 SGSoundSample *sample = sample_current->second;
413 sample->set_master_volume( _volume );
414 sample->set_orientation( _orientation );
415 sample->set_rotation( ec2body );
416 sample->set_position( position );
417 sample->set_velocity( velocity );
419 // Test if a sample is farther away than max distance, if so
420 // stop the sound playback and free it's source.
421 if (!_tied_to_listener) {
422 float max2 = sample->get_max_dist() * sample->get_max_dist();
423 float dist2 = position[0]*position[0]
424 + position[1]*position[1] + position[2]*position[2];
425 if ((dist2 > max2) && !sample->test_out_of_range()) {
426 sample->set_out_of_range(true);
427 } else if ((dist2 < max2) && sample->test_out_of_range()) {
428 sample->set_out_of_range(false);
434 void SGSampleGroup::update_sample_config( SGSoundSample *sample )
437 SGVec3f orientation, velocity;
440 if ( _tied_to_listener ) {
441 orientation = _smgr->get_direction();
442 position = SGVec3d::zeros();
443 velocity = _smgr->get_velocity();
445 sample->update_pos_and_orientation();
446 orientation = sample->get_orientation();
447 position = sample->get_position();
448 velocity = sample->get_velocity();
451 if (_smgr->bad_doppler_effect()) {
456 if (length(position) > 20000)
457 printf("%s source and listener distance greater than 20km!\n",
459 if (isNaN(toVec3f(position).data())) printf("NaN in source position\n");
460 if (isNaN(orientation.data())) printf("NaN in source orientation\n");
461 if (isNaN(velocity.data())) printf("NaN in source velocity\n");
464 unsigned int source = sample->get_source();
465 alSourcefv( source, AL_POSITION, toVec3f(position).data() );
466 alSourcefv( source, AL_VELOCITY, velocity.data() );
467 alSourcefv( source, AL_DIRECTION, orientation.data() );
468 testForALError("position and orientation");
470 alSourcef( source, AL_PITCH, sample->get_pitch() );
471 alSourcef( source, AL_GAIN, sample->get_volume() );
472 testForALError("pitch and gain");
474 if ( sample->has_static_data_changed() ) {
475 alSourcef( source, AL_CONE_INNER_ANGLE, sample->get_innerangle() );
476 alSourcef( source, AL_CONE_OUTER_ANGLE, sample->get_outerangle() );
477 alSourcef( source, AL_CONE_OUTER_GAIN, sample->get_outergain() );
478 testForALError("audio cone");
480 alSourcef( source, AL_MAX_DISTANCE, sample->get_max_dist() );
481 alSourcef( source, AL_REFERENCE_DISTANCE,
482 sample->get_reference_dist() );
483 testForALError("distance rolloff");
488 bool SGSampleGroup::testForError(void *p, string s)
491 SG_LOG( SG_SOUND, SG_ALERT, "Error (sample group): " << s);
497 bool SGSampleGroup::testForALError(string s)
500 ALenum error = alGetError();
501 if (error != AL_NO_ERROR) {
502 SG_LOG( SG_SOUND, SG_ALERT, "AL Error (" << _refname << "): "
503 << alGetString(error) << " at " << s);