1 // soundmgr_openal.cxx -- Sound effect management class for OpenAL
2 //
3 // Sound manager initially written by David Findlay
4 // <david_j_findlay@yahoo.com.au> 2001
5 //
6 // C++-ified by Curtis Olson, started March 2001.
7 // Modified for the new SoundSystem by Erik Hofman, October 2009
8 //
9 // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt
10 // Copyright (C) 2009 Erik Hofman <erik@ehofman.com>
11 //
12 // This program is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU General Public License as
14 // published by the Free Software Foundation; either version 2 of the
15 // License, or (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 // General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software Foundation,
24 // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // $Id$
27
28 #ifdef HAVE_CONFIG_H
29 # include <simgear_config.h>
30 #endif
31
32 #include <stdio.h>
33
34 #include <iostream>
35 #include <algorithm>
36 #include <cstring>
37 #include <cassert>
38
39 #include "soundmgr.hxx"
40 #include "readwav.hxx"
41 #include "sample_group.hxx"
42
43 #include <simgear/sg_inlines.h>
44 #include <simgear/structure/exception.hxx>
45 #include <simgear/debug/logstream.hxx>
46 #include <simgear/misc/sg_path.hxx>
47
48 #if defined(__APPLE__)
49 # include <OpenAL/al.h>
50 # include <OpenAL/alc.h>
51 #elif defined(OPENALSDK)
52 # include <al.h>
53 # include <alc.h>
54 #else
55 # include <AL/al.h>
56 # include <AL/alc.h>
57 #endif
58
59 using std::vector;
60
61
62 #define MAX_SOURCES 128
63
64 #ifndef ALC_ALL_DEVICES_SPECIFIER
65 # define ALC_ALL_DEVICES_SPECIFIER 0x1013
66 #endif
67 #ifndef AL_FORMAT_MONO_MULAW_EXT
68 # define AL_FORMAT_MONO_MULAW_EXT 0x10014
69 #endif
70 #ifndef AL_FORMAT_MONO_IMA4
71 # define AL_FORMAT_MONO_IMA4 0x1300
72 #endif
73 #ifndef AL_UNPACK_BLOCK_ALIGNMENT_SOFT
74 # define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C
75 #endif
76
77
78 struct refUint {
79 unsigned int refctr;
80 ALuint id;
81
refUintrefUint82 refUint() { refctr = 0; id = (ALuint)-1; };
refUintrefUint83 refUint(ALuint i) { refctr = 1; id = i; };
~refUintrefUint84 ~refUint() {};
85 };
86
87 using buffer_map = std::map < std::string, refUint >;
88 using sample_group_map = std::map < std::string, SGSharedPtr<SGSampleGroup> >;
89
isNaN(float * v)90 inline bool isNaN(float *v) {
91 return (SGMisc<float>::isNaN(v[0]) || SGMisc<float>::isNaN(v[1]) || SGMisc<float>::isNaN(v[2]));
92 }
93
94
95 class SGSoundMgr::SoundManagerPrivate
96 {
97 public:
98 SoundManagerPrivate() = default;
99 ~SoundManagerPrivate() = default;
100
init()101 void init()
102 {
103 _at_up_vec[0] = 0.0; _at_up_vec[1] = 0.0; _at_up_vec[2] = -1.0;
104 _at_up_vec[3] = 0.0; _at_up_vec[4] = 1.0; _at_up_vec[5] = 0.0;
105 }
106
update_pos_and_orientation()107 void update_pos_and_orientation()
108 {
109 /**
110 * Description: ORIENTATION is a pair of 3-tuples representing the
111 * 'at' direction vector and 'up' direction of the Object in
112 * Cartesian space. AL expects two vectors that are orthogonal to
113 * each other. These vectors are not expected to be normalized. If
114 * one or more vectors have zero length, implementation behavior
115 * is undefined. If the two vectors are linearly dependent,
116 * behavior is undefined.
117 *
118 * This is in the same coordinate system as OpenGL; y=up, z=back, x=right.
119 */
120 SGVec3d sgv_at = _orientation.backTransform(-SGVec3d::e3());
121 SGVec3d sgv_up = _orientation.backTransform(SGVec3d::e2());
122 _at_up_vec[0] = sgv_at[0];
123 _at_up_vec[1] = sgv_at[1];
124 _at_up_vec[2] = sgv_at[2];
125 _at_up_vec[3] = sgv_up[0];
126 _at_up_vec[4] = sgv_up[1];
127 _at_up_vec[5] = sgv_up[2];
128
129 _absolute_pos = _base_pos;
130 }
131
132 ALCdevice *_device = nullptr;
133 ALCcontext *_context = nullptr;
134
135 std::vector<ALuint> _free_sources;
136 std::vector<ALuint> _sources_in_use;
137
138 SGVec3d _absolute_pos = SGVec3d::zeros();
139 SGVec3d _base_pos = SGVec3d::zeros();
140 SGQuatd _orientation = SGQuatd::zeros();
141 // Orientation of the listener.
142 // first 3 elements are "at" vector, second 3 are "up" vector
143 ALfloat _at_up_vec[6];
144
145 bool _bad_doppler = false;
146
147 sample_group_map _sample_groups;
148 buffer_map _buffers;
149 };
150
151
152 //
153 // Sound Manager
154 //
155
156 // constructor
SGSoundMgr()157 SGSoundMgr::SGSoundMgr() {
158 d.reset(new SoundManagerPrivate);
159 d->_base_pos = SGVec3d::fromGeod(_geod_pos);
160
161 _block_support = alIsExtensionPresent((ALchar *)"AL_SOFT_block_alignment");
162 }
163
164 // destructor
165
~SGSoundMgr()166 SGSoundMgr::~SGSoundMgr() {
167
168 if (is_working())
169 stop();
170 d->_sample_groups.clear();
171 }
172
173 // initialize the sound manager
init()174 void SGSoundMgr::init()
175 {
176 #ifdef ENABLE_SOUND
177 if (is_working())
178 {
179 SG_LOG( SG_SOUND, SG_ALERT, "Oops, OpenAL sound manager is already initialized." );
180 return;
181 }
182
183 SG_LOG( SG_SOUND, SG_INFO, "Initializing OpenAL sound manager" );
184
185 d->_free_sources.clear();
186 d->_free_sources.reserve( MAX_SOURCES );
187 d->_sources_in_use.clear();
188 d->_sources_in_use.reserve( MAX_SOURCES );
189
190 ALCdevice *device = nullptr;
191 const char* devname = _device_name.c_str();
192 if (_device_name == "")
193 devname = nullptr; // use default device
194 else
195 {
196 // try non-default device
197 device = alcOpenDevice(devname);
198 }
199
200 if ((!devname)||(testForError(device, "Audio device not available, trying default.")) ) {
201 device = alcOpenDevice(nullptr);
202 if (testForError(device, "Default audio device not available.") ) {
203 return;
204 }
205 }
206
207 ALCcontext *context = alcCreateContext(device, nullptr);
208 testForALCError("context creation.");
209 if ( testForError(context, "Unable to create a valid context.") ) {
210 alcCloseDevice (device);
211 return;
212 }
213
214 if ( !alcMakeContextCurrent(context) ) {
215 testForALCError("context initialization");
216 alcDestroyContext (context);
217 alcCloseDevice (device);
218 return;
219 }
220
221 if (d->_context != nullptr)
222 {
223 SG_LOG(SG_SOUND, SG_ALERT, "context is already assigned");
224 }
225
226 d->_context = context;
227 d->_device = device;
228 d->init();
229
230 alListenerf( AL_GAIN, 0.0f );
231 alListenerfv( AL_ORIENTATION, d->_at_up_vec );
232 alListenerfv( AL_POSITION, SGVec3f::zeros().data() );
233 alListenerfv( AL_VELOCITY, SGVec3f::zeros().data() );
234
235 alDopplerFactor(1.0f);
236 alDopplerVelocity(_sound_velocity);
237
238 // gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE +
239 // AL_ROLLOFF_FACTOR * (distance - AL_REFERENCE_DISTANCE));
240 alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
241
242 testForError("listener initialization");
243
244 // get a free source one at a time
245 // if an error is returned no more (hardware) sources are available
246 for (unsigned int i=0; i<MAX_SOURCES; i++) {
247 ALuint source;
248 ALenum error;
249
250 alGetError();
251 alGenSources(1, &source);
252 error = alGetError();
253 if ( error == AL_NO_ERROR ) {
254 d->_free_sources.push_back( source );
255 } else {
256 SG_LOG(SG_SOUND, SG_INFO, "allocating source failed:" << i);
257 break;
258 }
259 }
260
261 _vendor = (const char *)alGetString(AL_VENDOR);
262 _renderer = (const char *)alGetString(AL_RENDERER);
263
264 if (_vendor == "Creative Labs Inc.") {
265 alDopplerFactor(100.0f);
266 d->_bad_doppler = true;
267 } else if (_vendor == "OpenAL Community" && _renderer == "OpenAL Soft") {
268 alDopplerFactor(100.0f);
269 d->_bad_doppler = true;
270 }
271
272 if (d->_free_sources.empty()) {
273 SG_LOG(SG_SOUND, SG_ALERT, "Unable to grab any OpenAL sources!");
274 }
275 #endif
276 }
277
reinit()278 void SGSoundMgr::reinit()
279 {
280 bool was_active = _active;
281
282 if (was_active)
283 {
284 suspend();
285 }
286
287 SGSoundMgr::stop();
288 SGSoundMgr::init();
289
290 if (was_active)
291 resume();
292 }
293
activate()294 void SGSoundMgr::activate()
295 {
296 #ifdef ENABLE_SOUND
297 if ( is_working() ) {
298 _active = true;
299
300 for ( auto current : d->_sample_groups ) {
301 current.second->activate();
302 }
303 }
304 #endif
305 }
306
307 // stop the sound manager
stop()308 void SGSoundMgr::stop()
309 {
310 #ifdef ENABLE_SOUND
311 // first stop all sample groups
312 for ( auto current : d->_sample_groups ) {
313 current.second->stop();
314 }
315
316 // clear all OpenAL sources
317 for(ALuint source : d->_free_sources) {
318 alDeleteSources( 1 , &source );
319 testForError("SGSoundMgr::stop: delete sources");
320 }
321 d->_free_sources.clear();
322
323 // clear any OpenAL buffers before shutting down
324 for ( auto current : d->_buffers ) {
325 refUint ref = current.second;
326 ALuint buffer = ref.id;
327 alDeleteBuffers(1, &buffer);
328 testForError("SGSoundMgr::stop: delete buffers");
329 }
330
331 d->_buffers.clear();
332 d->_sources_in_use.clear();
333
334 if (is_working()) {
335 _active = false;
336 alcDestroyContext(d->_context);
337 alcCloseDevice(d->_device);
338 d->_context = nullptr;
339 d->_device = nullptr;
340
341 _renderer = "unknown";
342 _vendor = "unknown";
343 }
344 #endif
345 }
346
suspend()347 void SGSoundMgr::suspend()
348 {
349 #ifdef ENABLE_SOUND
350 if (is_working()) {
351 for ( auto current : d->_sample_groups ) {
352 current.second->suspend();
353 }
354 _active = false;
355 }
356 #endif
357 }
358
resume()359 void SGSoundMgr::resume()
360 {
361 #ifdef ENABLE_SOUND
362 if (is_working()) {
363 for ( auto current : d->_sample_groups ) {
364 current.second->resume();
365 }
366 _active = true;
367 }
368 #endif
369 }
370
371 // run the audio scheduler
update(double dt)372 void SGSoundMgr::update( double dt )
373 {
374 #ifdef ENABLE_SOUND
375 if (_active) {
376 alcSuspendContext(d->_context);
377
378 if (_changed) {
379 d->update_pos_and_orientation();
380 }
381
382 for ( auto current : d->_sample_groups ) {
383 current.second->update(dt);
384 }
385
386 if (_changed) {
387 #if 0
388 if (isNaN(d->_at_up_vec)) printf("NaN in listener orientation\n");
389 if (isNaN(toVec3f(d->_absolute_pos).data())) printf("NaN in listener position\n");
390 if (isNaN(toVec3f(_velocity).data())) printf("NaN in listener velocity\n");
391 #endif
392 alListenerf( AL_GAIN, _volume );
393 alListenerfv( AL_ORIENTATION, d->_at_up_vec );
394 // alListenerfv( AL_POSITION, toVec3f(_absolute_pos).data() );
395
396 SGQuatd hlOr = SGQuatd::fromLonLat( _geod_pos );
397 SGVec3d velocity = SGVec3d::zeros();
398 if ( _velocity[0] || _velocity[1] || _velocity[2] ) {
399 velocity = hlOr.backTransform(_velocity*SG_FEET_TO_METER);
400
401 if ( d->_bad_doppler ) {
402 float fact = 100.0f;
403 float mag = length( velocity );
404
405 if (mag > _sound_velocity) {
406 fact *= _sound_velocity / mag;
407 }
408 alDopplerFactor(fact);
409 }
410 }
411
412 alListenerfv( AL_VELOCITY, toVec3f(velocity).data() );
413 // alDopplerVelocity(_sound_velocity);
414 testForError("update");
415 _changed = false;
416 }
417
418 alcProcessContext(d->_context);
419 }
420 #endif
421 }
422
423 // add a sample group, return true if successful
add(SGSampleGroup * sgrp,const std::string & refname)424 bool SGSoundMgr::add( SGSampleGroup *sgrp, const std::string& refname )
425 {
426 auto sample_grp_it = d->_sample_groups.find( refname );
427 if ( sample_grp_it != d->_sample_groups.end() ) {
428 // sample group already exists
429 return false;
430 }
431
432 if (_active) sgrp->activate();
433 d->_sample_groups[refname] = sgrp;
434
435 return true;
436 }
437
438
439 // remove a sound effect, return true if successful
remove(const std::string & refname)440 bool SGSoundMgr::remove( const std::string &refname )
441 {
442 auto sample_grp_it = d->_sample_groups.find( refname );
443 if ( sample_grp_it == d->_sample_groups.end() ) {
444 // sample group was not found.
445 return false;
446 }
447
448 d->_sample_groups.erase( sample_grp_it );
449
450 return true;
451 }
452
453
454 // return true of the specified sound exists in the sound manager system
exists(const std::string & refname)455 bool SGSoundMgr::exists( const std::string &refname ) {
456 auto sample_grp_it = d->_sample_groups.find( refname );
457 return ( sample_grp_it != d->_sample_groups.end() );
458 }
459
460
461 // return a pointer to the SGSampleGroup if the specified sound exists
462 // in the sound manager system, otherwise return nullptr
find(const std::string & refname,bool create)463 SGSampleGroup *SGSoundMgr::find( const std::string &refname, bool create ) {
464 auto sample_grp_it = d->_sample_groups.find( refname );
465 if ( sample_grp_it == d->_sample_groups.end() ) {
466 // sample group was not found.
467 if (create) {
468 SGSampleGroup* sgrp = new SGSampleGroup(this, refname);
469 add( sgrp, refname );
470 return sgrp;
471 }
472 else
473 return nullptr;
474 }
475
476 return sample_grp_it->second;
477 }
478
479
set_volume(float v)480 void SGSoundMgr::set_volume( float v )
481 {
482 _volume = v;
483 SG_CLAMP_RANGE(_volume, 0.0f, 1.0f);
484 _changed = true;
485 }
486
487 // Get an unused source id
488 //
489 // The Sound Manager should keep track of the sources in use, the distance
490 // of these sources to the listener and the volume (also based on audio cone
491 // and hence orientation) of the sources.
492 //
493 // The Sound Manager is (and should be) the only one knowing about source
494 // management. Sources further away should be suspended to free resources for
495 // newly added sounds close by.
request_source()496 unsigned int SGSoundMgr::request_source()
497 {
498 unsigned int source = NO_SOURCE;
499
500 if (!d->_free_sources.empty()) {
501 source = d->_free_sources.back();
502 d->_free_sources.pop_back();
503 d->_sources_in_use.push_back(source);
504 }
505 else
506 SG_LOG( SG_SOUND, SG_BULK, "Sound manager: No more free sources available!\n");
507
508 return source;
509 }
510
511 // Free up a source id for further use
release_source(unsigned int source)512 void SGSoundMgr::release_source( unsigned int source )
513 {
514 auto it = std::find(d->_sources_in_use.begin(), d->_sources_in_use.end(), source);
515 if ( it != d->_sources_in_use.end() ) {
516 #ifdef ENABLE_SOUND
517 ALint result;
518
519 alGetSourcei( source, AL_SOURCE_STATE, &result );
520 if ( result == AL_PLAYING || result == AL_PAUSED ) {
521 alSourceStop( source );
522 }
523
524 alSourcei( source, AL_BUFFER, 0 ); // detach the associated buffer
525 testForError("release_source");
526 #endif
527 d->_free_sources.push_back( source );
528 d->_sources_in_use.erase( it );
529 }
530 }
531
request_buffer(SGSoundSample * sample)532 unsigned int SGSoundMgr::request_buffer(SGSoundSample *sample)
533 {
534 ALuint buffer = NO_BUFFER;
535 #ifdef ENABLE_SOUND
536 if ( !sample->is_valid_buffer() ) {
537 // sample was not yet loaded or removed again
538 std::string sample_name = sample->get_sample_name();
539 void* sample_data = nullptr;
540
541 // see if the sample name is already cached
542 auto buffer_it = d->_buffers.find( sample_name );
543 if ( buffer_it != d->_buffers.end() ) {
544 buffer_it->second.refctr++;
545 buffer = buffer_it->second.id;
546 sample->set_buffer( buffer );
547 return buffer;
548 }
549
550 // sample name was not found in the buffer cache.
551 if ( sample->is_file() ) {
552 int freq, format, block;
553 size_t size;
554
555 try {
556 bool res = load(sample_name, &sample_data, &format, &size, &freq, &block);
557 if (res == false) return NO_BUFFER;
558 } catch (sg_exception& e) {
559 SG_LOG(SG_SOUND, SG_ALERT,
560 "failed to load sound buffer:\n" << e.getFormattedMessage());
561 sample->set_buffer( SGSoundMgr::FAILED_BUFFER );
562 return FAILED_BUFFER;
563 }
564
565 sample->set_block_align( block );
566 sample->set_frequency( freq );
567 sample->set_format( format );
568 sample->set_size( size );
569
570 } else {
571 sample_data = sample->get_data();
572 }
573
574 ALenum format = AL_NONE;
575 switch( sample->get_format() )
576 {
577 case SG_SAMPLE_MONO16:
578 format = AL_FORMAT_MONO16;
579 break;
580 case SG_SAMPLE_MONO8:
581 format = AL_FORMAT_MONO8;
582 break;
583 case SG_SAMPLE_MULAW:
584 format = AL_FORMAT_MONO_MULAW_EXT;
585 break;
586 case SG_SAMPLE_ADPCM:
587 format = AL_FORMAT_MONO_IMA4;
588 break;
589
590 case SG_SAMPLE_STEREO16:
591 SG_LOG(SG_SOUND, SG_POPUP, "Stereo sound detected:\n" << sample->get_sample_name() << "\nUse two separate mono files instead if required.");
592 format = AL_FORMAT_STEREO16;
593 break;
594 case SG_SAMPLE_STEREO8:
595 SG_LOG(SG_SOUND, SG_POPUP, "Stereo sound detected:\n" << sample->get_sample_name() << "\nUse two separate mono files instead if required.");
596 format = AL_FORMAT_STEREO8;
597 break;
598 default:
599 SG_LOG(SG_SOUND, SG_ALERT, "unsupported audio format");
600 return buffer;
601 }
602
603 // create an OpenAL buffer handle
604 alGenBuffers(1, &buffer);
605 if ( !testForError("generate buffer") ) {
606 // Copy data to the internal OpenAL buffer
607
608 ALsizei size = sample->get_size();
609 ALsizei freq = sample->get_frequency();
610 alBufferData( buffer, format, sample_data, size, freq );
611
612 if (format == AL_FORMAT_MONO_IMA4 && _block_support) {
613 ALsizei samples_block = BLOCKSIZE_TO_SMP( sample->get_block_align() );
614 alBufferi (buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, samples_block );
615 }
616
617 if ( !testForError("buffer add data") ) {
618 sample->set_buffer(buffer);
619 d->_buffers[sample_name] = refUint(buffer);
620 }
621 }
622
623 if ( sample->is_file() ) free(sample_data);
624 }
625 else {
626 buffer = sample->get_buffer();
627 }
628 #endif
629 return buffer;
630 }
631
release_buffer(SGSoundSample * sample)632 void SGSoundMgr::release_buffer(SGSoundSample *sample)
633 {
634 if ( !sample->is_queue() )
635 {
636 std::string sample_name = sample->get_sample_name();
637 auto buffer_it = d->_buffers.find( sample_name );
638 if ( buffer_it == d->_buffers.end() ) {
639 // buffer was not found
640 return;
641 }
642
643 sample->no_valid_buffer();
644 buffer_it->second.refctr--;
645 if (buffer_it->second.refctr == 0) {
646 #ifdef ENABLE_SOUND
647 ALuint buffer = buffer_it->second.id;
648 alDeleteBuffers(1, &buffer);
649 #endif
650 d->_buffers.erase( buffer_it );
651 testForError("release buffer");
652 }
653 }
654 }
655
sample_suspend(SGSoundSample * sample)656 void SGSoundMgr::sample_suspend( SGSoundSample *sample )
657 {
658 if ( sample->is_valid_source() && sample->is_playing() ) {
659 alSourcePause( sample->get_source() );
660 }
661 }
662
sample_resume(SGSoundSample * sample)663 void SGSoundMgr::sample_resume( SGSoundSample *sample )
664 {
665 if ( sample->is_valid_source() && sample->is_playing() ) {
666 alSourcePlay( sample->get_source() );
667 }
668 }
669
sample_init(SGSoundSample * sample)670 void SGSoundMgr::sample_init( SGSoundSample *sample )
671 {
672 #ifdef ENABLE_SOUND
673 //
674 // a request to start playing a sound has been filed.
675 //
676 ALuint source = request_source();
677 if (alIsSource(source) == AL_FALSE ) {
678 return;
679 }
680
681 sample->set_source( source );
682 #endif
683 }
684
sample_play(SGSoundSample * sample)685 void SGSoundMgr::sample_play( SGSoundSample *sample )
686 {
687 #ifdef ENABLE_SOUND
688 ALboolean looping = sample->is_looping() ? AL_TRUE : AL_FALSE;
689 ALint source = sample->get_source();
690
691 if ( !sample->is_queue() )
692 {
693 ALuint buffer = request_buffer(sample);
694 if (buffer == SGSoundMgr::FAILED_BUFFER ||
695 buffer == SGSoundMgr::NO_BUFFER)
696 {
697 release_source(source);
698 return;
699 }
700
701 // start playing the sample
702 buffer = sample->get_buffer();
703 if ( alIsBuffer(buffer) == AL_TRUE )
704 {
705 alSourcei( source, AL_BUFFER, buffer );
706 testForError("assign buffer to source");
707 } else
708 SG_LOG( SG_SOUND, SG_ALERT, "No such buffer!");
709 }
710
711 alSourcef( source, AL_ROLLOFF_FACTOR, 0.3 );
712 alSourcei( source, AL_LOOPING, looping );
713 alSourcei( source, AL_SOURCE_RELATIVE, AL_FALSE );
714 alSourcePlay( source );
715 testForError("sample play");
716 #endif
717 }
718
sample_stop(SGSoundSample * sample)719 void SGSoundMgr::sample_stop( SGSoundSample *sample )
720 {
721 if ( sample->is_valid_source() ) {
722 int source = sample->get_source();
723
724 bool stopped = is_sample_stopped(sample);
725 if ( sample->is_looping() && !stopped) {
726 #ifdef ENABLE_SOUND
727 alSourceStop( source );
728 #endif
729 stopped = is_sample_stopped(sample);
730 }
731
732 if ( stopped ) {
733 sample->no_valid_source();
734 release_source( source );
735 }
736 }
737 }
738
sample_destroy(SGSoundSample * sample)739 void SGSoundMgr::sample_destroy( SGSoundSample *sample )
740 {
741 if ( sample->is_valid_source() ) {
742 #ifdef ENABLE_SOUND
743 ALint source = sample->get_source();
744 if ( sample->is_playing() ) {
745 alSourceStop( source );
746 testForError("stop");
747 }
748 release_source( source );
749 #endif
750 sample->no_valid_source();
751 }
752
753 if ( sample->is_valid_buffer() ) {
754 release_buffer( sample );
755 sample->no_valid_buffer();
756 }
757 }
758
is_sample_stopped(SGSoundSample * sample)759 bool SGSoundMgr::is_sample_stopped(SGSoundSample *sample)
760 {
761 #ifdef ENABLE_SOUND
762 if ( sample->is_valid_source() ) {
763 ALint source = sample->get_source();
764 ALint result;
765 alGetSourcei( source, AL_SOURCE_STATE, &result );
766 return (result == AL_STOPPED);
767 }
768 #endif
769 return true;
770 }
771
update_sample_config(SGSoundSample * sample,SGVec3d & position,SGVec3f & orientation,SGVec3f & velocity)772 void SGSoundMgr::update_sample_config( SGSoundSample *sample, SGVec3d& position, SGVec3f& orientation, SGVec3f& velocity )
773 {
774 unsigned int source = sample->get_source();
775 alSourcefv( source, AL_POSITION, toVec3f(position).data() );
776 alSourcefv( source, AL_VELOCITY, velocity.data() );
777 alSourcefv( source, AL_DIRECTION, orientation.data() );
778 testForError("position and orientation");
779
780 alSourcef( source, AL_PITCH, sample->get_pitch() );
781 alSourcef( source, AL_GAIN, sample->get_volume() );
782 testForError("pitch and gain");
783
784 if ( sample->has_static_data_changed() ) {
785 alSourcef( source, AL_CONE_INNER_ANGLE, sample->get_innerangle() );
786 alSourcef( source, AL_CONE_OUTER_ANGLE, sample->get_outerangle() );
787 alSourcef( source, AL_CONE_OUTER_GAIN, sample->get_outergain() );
788 testForError("audio cone");
789
790 alSourcef( source, AL_MAX_DISTANCE, sample->get_max_dist() );
791 alSourcef( source, AL_REFERENCE_DISTANCE,
792 sample->get_reference_dist() );
793 testForError("distance rolloff");
794 }
795 }
796
797
load(const std::string & samplepath,void ** dbuf,int * fmt,size_t * sz,int * frq,int * block)798 bool SGSoundMgr::load( const std::string &samplepath,
799 void** dbuf,
800 int *fmt,
801 size_t *sz,
802 int *frq,
803 int *block )
804 {
805 if ( !is_working() )
806 return false;
807
808 unsigned int format;
809 unsigned int blocksz;
810 ALsizei size;
811 ALsizei freq;
812 ALfloat freqf;
813
814 auto data = simgear::loadWAVFromFile(samplepath, format, size, freqf, blocksz);
815 freq = (ALsizei)freqf;
816 if (!data) {
817 return false;
818 }
819
820 if (format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16) {
821 free(data);
822 SG_LOG(SG_IO, SG_DEV_ALERT, "Warning: STEREO files are not supported for 3D audio effects: " << samplepath);
823 return false;
824 }
825
826 *dbuf = (void *)data;
827 *fmt = (int)format;
828 *block = (int)blocksz;
829 *sz = (size_t)size;
830 *frq = (int)freq;
831
832 return true;
833 }
834
get_available_devices()835 vector<std::string> SGSoundMgr::get_available_devices()
836 {
837 vector<std::string> devices;
838 #ifdef ENABLE_SOUND
839 const ALCchar *s;
840
841 if (alcIsExtensionPresent(nullptr, "ALC_enumerate_all_EXT") == AL_TRUE) {
842 s = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
843 } else {
844 s = alcGetString(nullptr, ALC_DEVICE_SPECIFIER);
845 }
846
847 if (s) {
848 ALCchar *nptr, *ptr = (ALCchar *)s;
849
850 nptr = ptr;
851 while (*(nptr += strlen(ptr)+1) != 0)
852 {
853 devices.push_back(ptr);
854 ptr = nptr;
855 }
856 devices.push_back(ptr);
857 }
858 #endif
859 return devices;
860 }
861
862
testForError(void * p,std::string s)863 bool SGSoundMgr::testForError(void *p, std::string s)
864 {
865 if (p == nullptr) {
866 SG_LOG( SG_SOUND, SG_ALERT, "Error: " << s);
867 return true;
868 }
869 return false;
870 }
871
872
testForError(std::string s,std::string name)873 bool SGSoundMgr::testForError(std::string s, std::string name)
874 {
875 #ifdef ENABLE_SOUND
876 ALenum error = alGetError();
877 if (error != AL_NO_ERROR) {
878 SG_LOG( SG_SOUND, SG_ALERT, "AL Error (" << name << "): "
879 << alGetString(error) << " at " << s);
880 return true;
881 }
882 #endif
883 return false;
884 }
885
testForALCError(std::string s)886 bool SGSoundMgr::testForALCError(std::string s)
887 {
888 #ifdef ENABLE_SOUND
889 ALCenum error;
890 error = alcGetError(d->_device);
891 if (error != ALC_NO_ERROR) {
892 SG_LOG( SG_SOUND, SG_ALERT, "ALC Error (sound manager): "
893 << alcGetString(d->_device, error) << " at "
894 << s);
895 return true;
896 }
897 #endif
898 return false;
899 }
900
is_working() const901 bool SGSoundMgr::is_working() const
902 {
903 return (d->_device != nullptr);
904 }
905
get_orientation() const906 const SGQuatd& SGSoundMgr::get_orientation() const
907 {
908 return d->_orientation;
909 }
910
set_orientation(const SGQuatd & ori)911 void SGSoundMgr::set_orientation( const SGQuatd& ori )
912 {
913 d->_orientation = ori;
914 _changed = true;
915 }
916
get_position() const917 const SGVec3d& SGSoundMgr::get_position() const
918 {
919 return d->_absolute_pos;
920 }
921
set_position(const SGVec3d & pos,const SGGeod & pos_geod)922 void SGSoundMgr::set_position( const SGVec3d& pos, const SGGeod& pos_geod )
923 {
924 d->_base_pos = pos; _geod_pos = pos_geod; _changed = true;
925 }
926
get_direction() const927 SGVec3f SGSoundMgr::get_direction() const
928 {
929 return SGVec3f(d->_at_up_vec[0], d->_at_up_vec[1], d->_at_up_vec[2]);
930 }
931