]> git.mxchange.org Git - simgear.git/blob - simgear/sound/sample_group.cxx
sound: fix "source" resource leak
[simgear.git] / simgear / sound / sample_group.cxx
1 // sample_group.cxx -- Manage a group of samples relative to a base position
2 //
3 // Written for the new SoundSystem by Erik Hofman, October 2009
4 //
5 // Copyright (C) 2009 Erik Hofman <erik@ehofman.com>
6 //
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.
11 //
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.
16 //
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.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include <simgear_config.h>
25 #endif
26
27 #include <simgear/compiler.h>
28
29 #include "soundmgr_openal.hxx"
30 #include "sample_group.hxx"
31
32 bool isNaN(float *v) {
33    return (isnan(v[0]) || isnan(v[1]) || isnan(v[2]));
34 }
35
36 SGSampleGroup::SGSampleGroup () :
37     _smgr(NULL),
38     _refname(""),
39     _active(false),
40     _changed(false),
41     _pause(false),
42     _volume(1.0),
43     _tied_to_listener(false),
44     _velocity(SGVec3d::zeros()),
45     _orientation(SGQuatd::zeros())
46 {
47     _samples.clear();
48 }
49
50 SGSampleGroup::SGSampleGroup ( SGSoundMgr *smgr, const string &refname ) :
51     _smgr(smgr),
52     _refname(refname),
53     _active(false), 
54     _changed(false),
55     _pause(false),
56     _volume(1.0),
57     _tied_to_listener(false),
58     _velocity(SGVec3d::zeros()),
59     _orientation(SGQuatd::zeros())
60 {
61     _smgr->add(this, refname);
62     _samples.clear();
63 }
64
65 SGSampleGroup::~SGSampleGroup ()
66 {
67     _active = false;
68
69     sample_map_iterator sample_current = _samples.begin();
70     sample_map_iterator sample_end = _samples.end();
71     for ( ; sample_current != sample_end; ++sample_current ) {
72         SGSoundSample *sample = sample_current->second;
73
74         if ( sample->is_valid_source() && sample->is_playing() ) {
75             sample->no_valid_source();
76             _smgr->release_source( sample->get_source() );
77             _smgr->release_buffer( sample );
78         }
79     }
80
81     _smgr = 0;
82 }
83
84 void SGSampleGroup::update( double dt ) {
85
86     if ( !_active || _pause ) return;
87
88     testForALError("start of update!!\n");
89
90     // Delete any OpenAL buffers that might still be in use.
91     unsigned int size = _removed_samples.size();
92     for (unsigned int i=0; i<size; ) {
93         SGSoundSample *sample = _removed_samples[i];
94         ALint result = AL_STOPPED;
95
96         if ( sample->is_valid_source() ) {
97             int source = sample->get_source();
98
99             alGetSourcei( source, AL_SOURCE_STATE, &result );
100             if ( sample->is_looping() ) {
101                 if ( result != AL_STOPPED ) {
102                     alSourceStop( source );
103                     alGetSourcei( source, AL_SOURCE_STATE, &result );
104                 }
105                 if ( result == AL_STOPPED ) {
106                     sample->no_valid_source();
107                     _smgr->release_source( sample->get_source() );
108                 }
109             }
110         }
111
112         if ( result == AL_STOPPED ) {
113             sample->stop();
114             if ( !sample->is_queue() ) {
115                 ALuint buffer = sample->get_buffer();
116                 alDeleteBuffers( 1, &buffer );
117                 testForALError("buffer remove");
118             }
119             _removed_samples.erase( _removed_samples.begin()+i );
120             size--;
121             continue;
122         }
123         i++;
124     }
125
126     // Update the position and orientation information for all samples.
127     if ( _changed || _smgr->has_changed() ) {
128         update_pos_and_orientation();
129         _changed = false;
130     }
131
132     sample_map_iterator sample_current = _samples.begin();
133     sample_map_iterator sample_end = _samples.end();
134     for ( ; sample_current != sample_end; ++sample_current ) {
135         SGSoundSample *sample = sample_current->second;
136
137         if ( !sample->is_valid_source() && sample->is_playing() ) {
138             //
139             // a request to start playing a sound has been filed.
140             //
141             ALuint source = _smgr->request_source();
142             if (alIsSource(source) == AL_TRUE )
143             {
144                 ALboolean looping = sample->is_looping() ? AL_TRUE : AL_FALSE;
145                 if ( sample->is_queue() )
146                 {
147                     sample->set_source( source );
148                     update_sample_config( sample );
149
150                     alSourcef( source, AL_ROLLOFF_FACTOR, 0.3 );
151                     alSourcei( source, AL_LOOPING, looping );
152                     alSourcei( source, AL_SOURCE_RELATIVE, AL_FALSE );
153                     alSourcePlay( source );
154                     testForALError("sample play");
155                 }
156                 else
157                 {
158                     if (_smgr->request_buffer(sample) == SGSoundMgr::NO_BUFFER)
159                     {
160                         _smgr->release_source(source);
161                         continue;
162                     }
163
164                     // start playing the sample
165                     ALuint buffer = sample->get_buffer();
166                     if ( alIsBuffer(buffer) == AL_TRUE )
167                     {
168                         alSourcei( source, AL_BUFFER, buffer );
169                         testForALError("assign buffer to source");
170
171                         sample->set_source( source );
172                         update_sample_config( sample );
173
174                         alSourcei( source, AL_LOOPING, looping );
175                         alSourcef( source, AL_ROLLOFF_FACTOR, 0.3 );
176                         alSourcei( source, AL_SOURCE_RELATIVE, AL_FALSE );
177                         alSourcePlay( source );
178                         testForALError("sample play");
179                     } else
180                         SG_LOG( SG_GENERAL, SG_ALERT, "No such buffer!\n");
181                 }
182             }
183
184         } else if ( sample->is_valid_source() ) {
185             // check if the sound has stopped by itself
186
187             unsigned int source = sample->get_source();
188             int result;
189
190             alGetSourcei( source, AL_SOURCE_STATE, &result );
191             if ( result == AL_STOPPED ) {
192                 // sample is stoped because it wasn't looping
193                 sample->stop();
194                 sample->no_valid_source();
195                 _smgr->release_source( source );
196                 _smgr->release_buffer( sample );
197                 remove( sample->get_sample_name() );
198             }
199             else
200             if ( sample->has_changed() ) {
201                 if ( !sample->is_playing() ) {
202                     // a request to stop playing the sound has been filed.
203                     sample->stop();
204                     sample->no_valid_source();
205                     _smgr->release_source( sample->get_source() );
206                 } else if ( _smgr->has_changed() ) {
207                     update_sample_config( sample );
208                 }
209             }
210
211         }
212         testForALError("update");
213     }
214 }
215
216 // add a sound effect, return true if successful
217 bool SGSampleGroup::add( SGSharedPtr<SGSoundSample> sound, const string& refname ) {
218
219     sample_map_iterator sample_it = _samples.find( refname );
220     if ( sample_it != _samples.end() ) {
221         // sample name already exists
222         return false;
223     }
224
225     _samples[refname] = sound;
226     return true;
227 }
228
229
230 // remove a sound effect, return true if successful
231 bool SGSampleGroup::remove( const string &refname ) {
232
233     sample_map_iterator sample_it = _samples.find( refname );
234     if ( sample_it == _samples.end() ) {
235         // sample was not found
236         return false;
237     }
238
239     if ( sample_it->second->is_valid_buffer() )
240         _removed_samples.push_back( sample_it->second );
241
242     _samples.erase( sample_it );
243
244     return true;
245 }
246
247
248 // return true of the specified sound exists in the sound manager system
249 bool SGSampleGroup::exists( const string &refname ) {
250     sample_map_iterator sample_it = _samples.find( refname );
251     if ( sample_it == _samples.end() ) {
252         // sample was not found
253         return false;
254     }
255
256     return true;
257 }
258
259
260 // return a pointer to the SGSoundSample if the specified sound exists
261 // in the sound manager system, otherwise return NULL
262 SGSoundSample *SGSampleGroup::find( const string &refname ) {
263     sample_map_iterator sample_it = _samples.find( refname );
264     if ( sample_it == _samples.end() ) {
265         // sample was not found
266         return NULL;
267     }
268
269     return sample_it->second;
270 }
271
272
273 void
274 SGSampleGroup::stop ()
275 {
276     _pause = true;
277     sample_map_iterator sample_current = _samples.begin();
278     sample_map_iterator sample_end = _samples.end();
279     for ( ; sample_current != sample_end; ++sample_current ) {
280         SGSoundSample *sample = sample_current->second;
281
282         if ( sample->is_valid_source() ) {
283             ALint source = sample->get_source();
284             if ( sample->is_playing() ) {
285                 alSourceStop( source );
286             }
287             _smgr->release_source( source );
288             sample->no_valid_source();
289         }
290
291         if ( sample->is_valid_buffer() ) {
292             _smgr->release_buffer( sample );
293             sample->no_valid_buffer();
294         }
295     }
296     testForALError("stop");
297 }
298
299 // stop playing all associated samples
300 void
301 SGSampleGroup::suspend ()
302 {
303     if (_active && _pause == false) {
304         _pause = true;
305         sample_map_iterator sample_current = _samples.begin();
306         sample_map_iterator sample_end = _samples.end();
307         for ( ; sample_current != sample_end; ++sample_current ) {
308             SGSoundSample *sample = sample_current->second;
309
310             if ( sample->is_valid_source() && sample->is_playing() ) {
311                 alSourcePause( sample->get_source() );
312             }
313         }
314         testForALError("suspend");
315     }
316 }
317
318 // resume playing all associated samples
319 void
320 SGSampleGroup::resume ()
321 {
322     if (_active && _pause == true) {
323         sample_map_iterator sample_current = _samples.begin();
324         sample_map_iterator sample_end = _samples.end();
325         for ( ; sample_current != sample_end; ++sample_current ) {
326             SGSoundSample *sample = sample_current->second;
327
328             if ( sample->is_valid_source() && sample->is_playing() ) {
329                 alSourcePlay( sample->get_source() );
330             }
331         }
332         testForALError("resume");
333         _pause = false;
334     }
335 }
336
337
338 // tell the scheduler to play the indexed sample in a continuous loop
339 bool SGSampleGroup::play( const string &refname, bool looping = false ) {
340     SGSoundSample *sample = find( refname );
341
342     if ( sample == NULL ) {
343         return false;
344     }
345
346     sample->play( looping );
347     return true;
348 }
349
350
351 // return true of the specified sound is currently being played
352 bool SGSampleGroup::is_playing( const string& refname ) {
353     SGSoundSample *sample = find( refname );
354
355     if ( sample == NULL ) {
356         return false;
357     }
358
359     return ( sample->is_playing() ) ? true : false;
360 }
361
362 // immediate stop playing the sound
363 bool SGSampleGroup::stop( const string& refname ) {
364     SGSoundSample *sample  = find( refname );
365
366     if ( sample == NULL ) {
367         return false;
368     }
369
370     sample->stop();
371     return true;
372 }
373
374 void SGSampleGroup::set_volume( float vol )
375 {
376     if (vol > _volume*1.01 || vol < _volume*0.99) {
377         _volume = vol;
378         if (_volume < 0.0) _volume = 0.0;
379         if (_volume > 1.0) _volume = 1.0;
380         _changed = true;
381     }
382 }
383
384 // set the source position and orientation of all managed sounds
385 void SGSampleGroup::update_pos_and_orientation() {
386  
387     SGVec3d position = SGVec3d::fromGeod(_base_pos) - _smgr->get_position();
388     SGQuatd hlOr = SGQuatd::fromLonLat(_base_pos);
389     SGQuatd ec2body = hlOr*_orientation;
390
391     SGVec3f velocity = SGVec3f::zeros();
392     if ( _velocity[0] || _velocity[1] || _velocity[2] ) {
393        velocity = toVec3f( hlOr.backTransform(_velocity*SG_FEET_TO_METER) );
394     }
395
396     sample_map_iterator sample_current = _samples.begin();
397     sample_map_iterator sample_end = _samples.end();
398     for ( ; sample_current != sample_end; ++sample_current ) {
399         SGSoundSample *sample = sample_current->second;
400         sample->set_master_volume( _volume );
401         sample->set_orientation( _orientation );
402         sample->set_rotation( ec2body );
403         sample->set_position( position );
404         sample->set_velocity( velocity );
405     }
406 }
407
408 void SGSampleGroup::update_sample_config( SGSoundSample *sample ) {
409     SGVec3f orientation, velocity;
410     SGVec3d position;
411
412     if ( _tied_to_listener ) {
413         orientation = _smgr->get_direction();
414         position = SGVec3d::zeros();
415         velocity = _smgr->get_velocity();
416     } else {
417         sample->update_pos_and_orientation();
418         orientation = sample->get_orientation();
419         position = sample->get_position();
420         velocity = sample->get_velocity();
421     }
422
423     if (_smgr->bad_doppler_effect()) {
424         velocity *= 100.0f;
425     }
426
427 #if 0
428     if (length(position) > 20000)
429         printf("%s source and listener distance greater than 20km!\n",
430                _refname.c_str());
431     if (isNaN(toVec3f(position).data())) printf("NaN in source position\n");
432     if (isNaN(orientation.data())) printf("NaN in source orientation\n");
433     if (isNaN(velocity.data())) printf("NaN in source velocity\n");
434 #endif
435
436     unsigned int source = sample->get_source();
437     alSourcefv( source, AL_POSITION, toVec3f(position).data() );
438     alSourcefv( source, AL_VELOCITY, velocity.data() );
439     alSourcefv( source, AL_DIRECTION, orientation.data() );
440     testForALError("position and orientation");
441
442     alSourcef( source, AL_PITCH, sample->get_pitch() );
443     alSourcef( source, AL_GAIN, sample->get_volume() );
444     testForALError("pitch and gain");
445
446     if ( sample->has_static_data_changed() ) {
447         alSourcef( source, AL_CONE_INNER_ANGLE, sample->get_innerangle() );
448         alSourcef( source, AL_CONE_OUTER_ANGLE, sample->get_outerangle() );
449         alSourcef( source, AL_CONE_OUTER_GAIN, sample->get_outergain() );
450         testForALError("audio cone");
451
452         alSourcef( source, AL_MAX_DISTANCE, sample->get_max_dist() );
453         alSourcef( source, AL_REFERENCE_DISTANCE,
454                            sample->get_reference_dist() );
455         testForALError("distance rolloff");
456     }
457 }
458
459 bool SGSampleGroup::testForError(void *p, string s)
460 {
461    if (p == NULL) {
462       SG_LOG( SG_GENERAL, SG_ALERT, "Error (sample group): " << s);
463       return true;
464    }
465    return false;
466 }
467
468 bool SGSampleGroup::testForALError(string s)
469 {
470     ALenum error = alGetError();
471     if (error != AL_NO_ERROR)  {
472        SG_LOG( SG_GENERAL, SG_ALERT, "AL Error (" << _refname << "): "
473                                       << alGetString(error) << " at " << s);
474        return true;
475     }
476     return false;
477 }
478