mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 19:08:55 -04:00

We use resent OpenAL-soft in the distribution (1.12 or more instead of 1.8) There is this bug: OpenAl soft some times selects ALSA software ALSO software some times has a bug working with pulse audio So we need to make sure OpenAL uses pulse audio directoly, then it works. KittCat (openAL soft author): well, more like worked around it. but it's not something I can fix in openal soft I've gone over the code several times and had other people comment on it. alsa just enables mmap when using the pulse plugin even though the plugin doesn't support it using the pulse api directly (or disable using mmap with alsa) gets around the problem, though
1081 lines
36 KiB
C++
1081 lines
36 KiB
C++
// Filename: openalAudioManager.cxx
|
|
// Created by: Ben Buchwald <bb2@alumni.cmu.edu>
|
|
//
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
//
|
|
// All use of this software is subject to the terms of the revised BSD
|
|
// license. You should have received a copy of this license along
|
|
// with this source code in a file named "LICENSE."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "pandabase.h"
|
|
|
|
#ifdef HAVE_OPENAL //[
|
|
|
|
//Panda headers.
|
|
#include "config_audio.h"
|
|
#include "config_util.h"
|
|
#include "config_express.h"
|
|
#include "openalAudioManager.h"
|
|
#include "openalAudioSound.h"
|
|
#include "virtualFileSystem.h"
|
|
#include "movieAudio.h"
|
|
|
|
#include <algorithm>
|
|
|
|
TypeHandle OpenALAudioManager::_type_handle;
|
|
|
|
int OpenALAudioManager::_active_managers = 0;
|
|
bool OpenALAudioManager::_openal_active = false;
|
|
ALCdevice* OpenALAudioManager::_device = NULL;
|
|
ALCcontext* OpenALAudioManager::_context = NULL;
|
|
|
|
// This is the list of all OpenALAudioManager objects in the world. It
|
|
// must be a pointer rather than a concrete object, so it won't be
|
|
// destructed at exit time before we're done removing things from it.
|
|
OpenALAudioManager::Managers *OpenALAudioManager::_managers = NULL;
|
|
|
|
OpenALAudioManager::SourceCache *OpenALAudioManager::_al_sources = NULL;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Central dispatcher for audio errors.
|
|
////////////////////////////////////////////////////////////////////
|
|
void al_audio_errcheck(const char *context) {
|
|
ALenum result = alGetError();
|
|
if (result != AL_NO_ERROR) {
|
|
audio_error(context << ": " << alGetString(result) );
|
|
}
|
|
}
|
|
|
|
void alc_audio_errcheck(const char *context,ALCdevice* device) {
|
|
ALCenum result = alcGetError(device);
|
|
if (result != ALC_NO_ERROR) {
|
|
audio_error(context << ": " << alcGetString(device,result) );
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Create_OpenALAudioManager
|
|
// Access: Private
|
|
// Description: Factory Function
|
|
////////////////////////////////////////////////////////////////////
|
|
AudioManager *Create_OpenALAudioManager() {
|
|
audio_debug("Create_OpenALAudioManager()");
|
|
return new OpenALAudioManager;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
OpenALAudioManager::
|
|
OpenALAudioManager() {
|
|
if (_managers == (Managers *)NULL) {
|
|
_managers = new Managers;
|
|
_al_sources = new SourceCache;
|
|
}
|
|
|
|
_managers->insert(this);
|
|
|
|
_cleanup_required = true;
|
|
_active = audio_active;
|
|
_volume = audio_volume;
|
|
_play_rate = 1.0f;
|
|
|
|
_cache_limit = audio_cache_limit;
|
|
|
|
_concurrent_sound_limit = 0;
|
|
_is_valid = true;
|
|
|
|
//Init 3D attributes
|
|
_distance_factor = 3.28;
|
|
_drop_off_factor = 1;
|
|
|
|
_position[0] = 0;
|
|
_position[1] = 0;
|
|
_position[2] = 0;
|
|
|
|
_velocity[0] = 0;
|
|
_velocity[1] = 0;
|
|
_velocity[2] = 0;
|
|
|
|
_forward_up[0] = 0;
|
|
_forward_up[1] = 0;
|
|
_forward_up[2] = 0;
|
|
_forward_up[3] = 0;
|
|
_forward_up[4] = 0;
|
|
_forward_up[5] = 0;
|
|
|
|
// Initialization
|
|
if (_active_managers==0 || !_openal_active) {
|
|
_device = alcOpenDevice(NULL); // select the "preferred device"
|
|
if (!_device) {
|
|
// this is a unique kind of error
|
|
audio_error("OpenALAudioManager: alcOpenDevice(NULL): ALC couldn't open device");
|
|
} else {
|
|
alcGetError(_device); // clear errors
|
|
_context=alcCreateContext(_device,NULL);
|
|
alc_audio_errcheck("alcCreateContext(_device,NULL)",_device);
|
|
if (_context!=NULL) {
|
|
_openal_active = true;
|
|
}
|
|
}
|
|
}
|
|
// We increment _active_managers regardless of possible errors above.
|
|
// The shutdown call will do the right thing when it's called,
|
|
// either way.
|
|
++_active_managers;
|
|
nassertv(_active_managers>0);
|
|
|
|
if (!_device || !_context) {
|
|
audio_error("OpenALAudioManager: No open device or context");
|
|
_is_valid = false;
|
|
} else {
|
|
alcGetError(_device); // clear errors
|
|
alcMakeContextCurrent(_context);
|
|
alc_audio_errcheck("alcMakeContextCurrent(_context)",_device);
|
|
|
|
|
|
// set 3D sound characteristics as they are given in the configrc
|
|
audio_3d_set_doppler_factor(audio_doppler_factor);
|
|
audio_3d_set_distance_factor(audio_distance_factor);
|
|
audio_3d_set_drop_off_factor(audio_drop_off_factor);
|
|
}
|
|
|
|
audio_cat->debug() << "ALC_DEVICE_SPECIFIER:" << alcGetString(_device, ALC_DEVICE_SPECIFIER) << endl;
|
|
audio_cat->debug() << "AL_RENDERER:" << alGetString(AL_RENDERER) << endl;
|
|
audio_cat->debug() << "AL_VENDOR:" << alGetString(AL_VENDOR) << endl;
|
|
audio_cat->debug() << "AL_VERSION:" << alGetString(AL_VERSION) << endl;
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::Destructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
OpenALAudioManager::
|
|
~OpenALAudioManager() {
|
|
nassertv(_managers != (Managers *)NULL);
|
|
Managers::iterator mi = _managers->find(this);
|
|
nassertv(mi != _managers->end());
|
|
_managers->erase(mi);
|
|
cleanup();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::shutdown
|
|
// Access: Published, Virtual
|
|
// Description: Call this at exit time to shut down the audio system.
|
|
// This will invalidate all currently-active
|
|
// AudioManagers and AudioSounds in the system. If you
|
|
// change your mind and want to play sounds again, you
|
|
// will have to recreate all of these objects.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
shutdown() {
|
|
if (_managers != (Managers *)NULL) {
|
|
Managers::iterator mi;
|
|
for (mi = _managers->begin(); mi != _managers->end(); ++mi) {
|
|
(*mi)->cleanup();
|
|
}
|
|
}
|
|
|
|
nassertv(_active_managers == 0);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::is_valid
|
|
// Access:
|
|
// Description: This is mostly for debugging, but it it could be
|
|
// used to detect errors in a release build if you
|
|
// don't mind the cpu cost.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool OpenALAudioManager::
|
|
is_valid() {
|
|
return _is_valid;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::make_current
|
|
// Access: Private
|
|
// Description: This makes this manager's OpenAL context the
|
|
// current context. Needed before any parameter sets.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
make_current() const {
|
|
// Since we only use one context, this is now a no-op.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::can_use_audio
|
|
// Access: Private
|
|
// Description: Returns true if the specified MovieAudioCursor
|
|
// can be used by this AudioManager. Mostly, this
|
|
// involves checking whether or not the format is
|
|
// implemented/supported.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool OpenALAudioManager::
|
|
can_use_audio(MovieAudioCursor *source) {
|
|
int channels = source->audio_channels();
|
|
if ((channels != 1)&&(channels != 2)) {
|
|
audio_error("Currently, only mono and stereo are supported.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::should_load_audio
|
|
// Access: Private
|
|
// Description: Returns true if the specified MovieAudio should be
|
|
// cached into RAM. A lot of conditions have to be met
|
|
// in order to allow caching - if any are not met,
|
|
// the file will be streamed.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool OpenALAudioManager::
|
|
should_load_audio(MovieAudioCursor *source, int mode) {
|
|
if (mode == SM_stream) {
|
|
// If the user asked for streaming, give him streaming.
|
|
return false;
|
|
}
|
|
if (source->get_source()->get_filename().empty()) {
|
|
// Non-files cannot be preloaded.
|
|
return false;
|
|
}
|
|
if (source->ready() != 0x40000000) {
|
|
// Streaming sources cannot be preloaded.
|
|
return false;
|
|
}
|
|
if (source->length() > 3600.0) {
|
|
// Anything longer than an hour cannot be preloaded.
|
|
return false;
|
|
}
|
|
int channels = source->audio_channels();
|
|
int samples = (int)(source->length() * source->audio_rate());
|
|
int bytes = samples * channels * 2;
|
|
if ((mode == SM_heuristic)&&(bytes > audio_preload_threshold)) {
|
|
// In heuristic mode, if file is long, stream it.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_sound_data
|
|
// Access: Private
|
|
// Description: Obtains a SoundData for the specified sound.
|
|
//
|
|
// When you are done with the SoundData, you need
|
|
// to decrement the client count.
|
|
////////////////////////////////////////////////////////////////////
|
|
OpenALAudioManager::SoundData *OpenALAudioManager::
|
|
get_sound_data(MovieAudio *movie, int mode) {
|
|
const Filename &path = movie->get_filename();
|
|
|
|
// Search for an already-cached sample or an already-opened stream.
|
|
if (!path.empty()) {
|
|
|
|
if (mode != SM_stream) {
|
|
SampleCache::iterator lsmi=_sample_cache.find(path);
|
|
if (lsmi != _sample_cache.end()) {
|
|
SoundData *sd = (*lsmi).second;
|
|
increment_client_count(sd);
|
|
return sd;
|
|
}
|
|
}
|
|
|
|
if (mode != SM_sample) {
|
|
ExpirationQueue::iterator exqi;
|
|
for (exqi=_expiring_streams.begin(); exqi!=_expiring_streams.end(); exqi++) {
|
|
SoundData *sd = (SoundData*)(*exqi);
|
|
if (sd->_movie->get_filename() == path) {
|
|
increment_client_count(sd);
|
|
return sd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PT(MovieAudioCursor) stream = movie->open();
|
|
if (stream == 0) {
|
|
audio_error("Cannot open file: "<<path);
|
|
return NULL;
|
|
}
|
|
|
|
if (!can_use_audio(stream)) {
|
|
audio_error("File is not in usable format: "<<path);
|
|
return NULL;
|
|
}
|
|
|
|
SoundData *sd = new SoundData();
|
|
sd->_client_count = 1;
|
|
sd->_manager = this;
|
|
sd->_movie = movie;
|
|
sd->_rate = stream->audio_rate();
|
|
sd->_channels = stream->audio_channels();
|
|
sd->_length = stream->length();
|
|
audio_debug("Creating: " << sd->_movie->get_filename().get_basename());
|
|
audio_debug(" - Rate: " << sd->_rate);
|
|
audio_debug(" - Channels: " << sd->_channels);
|
|
audio_debug(" - Length: " << sd->_length);
|
|
|
|
if (should_load_audio(stream, mode)) {
|
|
audio_debug(path.get_basename() << ": loading as sample");
|
|
make_current();
|
|
alGetError(); // clear errors
|
|
sd->_sample = 0;
|
|
alGenBuffers(1, &sd->_sample);
|
|
al_audio_errcheck("alGenBuffers");
|
|
if (sd->_sample == 0) {
|
|
audio_error("Could not create an OpenAL buffer object");
|
|
delete sd;
|
|
return NULL;
|
|
}
|
|
int channels = stream->audio_channels();
|
|
int samples = (int)(stream->length() * stream->audio_rate());
|
|
PN_int16 *data = new PN_int16[samples * channels];
|
|
stream->read_samples(samples, data);
|
|
alBufferData(sd->_sample,
|
|
(channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
|
|
data, samples * channels * 2, stream->audio_rate());
|
|
int err = alGetError();
|
|
if (err != AL_NO_ERROR) {
|
|
audio_error("could not fill OpenAL buffer object with data");
|
|
delete sd;
|
|
return NULL;
|
|
}
|
|
_sample_cache.insert(SampleCache::value_type(path, sd));
|
|
} else {
|
|
audio_debug(path.get_basename() << ": loading as stream");
|
|
sd->_stream = stream;
|
|
}
|
|
|
|
return sd;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_sound
|
|
// Access: Public
|
|
// Description: This is what creates a sound instance.
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(AudioSound) OpenALAudioManager::
|
|
get_sound(MovieAudio *sound, bool positional, int mode) {
|
|
if(!is_valid()) {
|
|
return get_null_sound();
|
|
}
|
|
PT(OpenALAudioSound) oas =
|
|
new OpenALAudioSound(this, sound, positional, mode);
|
|
|
|
_all_sounds.insert(oas);
|
|
PT(AudioSound) res = (AudioSound*)(OpenALAudioSound*)oas;
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_sound
|
|
// Access: Public
|
|
// Description: This is what creates a sound instance.
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(AudioSound) OpenALAudioManager::
|
|
get_sound(const string &file_name, bool positional, int mode) {
|
|
if(!is_valid()) {
|
|
return get_null_sound();
|
|
}
|
|
|
|
Filename path = file_name;
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
vfs->resolve_filename(path, get_model_path());
|
|
|
|
if (path.empty()) {
|
|
audio_error("get_sound - invalid filename");
|
|
return NULL;
|
|
}
|
|
|
|
PT(MovieAudio) mva = MovieAudio::get(path);
|
|
|
|
PT(OpenALAudioSound) oas =
|
|
new OpenALAudioSound(this, mva, positional, mode);
|
|
|
|
_all_sounds.insert(oas);
|
|
PT(AudioSound) res = (AudioSound*)(OpenALAudioSound*)oas;
|
|
return res;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::uncache_sound
|
|
// Access: Public
|
|
// Description: Deletes a sample from the expiration queues.
|
|
// If the sound is actively in use, then the sound
|
|
// cannot be deleted, and this function has no effect.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
uncache_sound(const string& file_name) {
|
|
assert(is_valid());
|
|
Filename path = file_name;
|
|
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
vfs->resolve_filename(path, get_model_path());
|
|
|
|
SampleCache::iterator sci = _sample_cache.find(path);
|
|
if (sci != _sample_cache.end()) {
|
|
SoundData *sd = (*sci).second;
|
|
if (sd->_client_count == 0) {
|
|
_expiring_samples.erase(sd->_expire);
|
|
_sample_cache.erase(sci);
|
|
delete sd;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::clear_cache
|
|
// Access: Public
|
|
// Description: Clear out the sound cache.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
clear_cache() {
|
|
discard_excess_cache(0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::set_cache_limit
|
|
// Access: Public
|
|
// Description: Set the number of sounds that the cache can hold.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
set_cache_limit(unsigned int count) {
|
|
_cache_limit=count;
|
|
discard_excess_cache(count);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_cache_limit
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
unsigned int OpenALAudioManager::
|
|
get_cache_limit() const {
|
|
return _cache_limit;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::release_sound
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
release_sound(OpenALAudioSound* audioSound) {
|
|
AllSounds::iterator ai = _all_sounds.find(audioSound);
|
|
if (ai != _all_sounds.end()) {
|
|
_all_sounds.erase(ai);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::set_volume(float volume)
|
|
// Access: Public
|
|
// Description:
|
|
// Sets listener gain
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::set_volume(float volume) {
|
|
if (_volume!=volume) {
|
|
_volume = volume;
|
|
|
|
// Tell our AudioSounds to adjust:
|
|
AllSounds::iterator i=_all_sounds.begin();
|
|
for (; i!=_all_sounds.end(); ++i) {
|
|
(**i).set_volume((**i).get_volume());
|
|
}
|
|
|
|
/*
|
|
// this was neat alternative to the above look
|
|
// when we had a seperate context for each manager
|
|
make_current();
|
|
|
|
alGetError(); // clear errors
|
|
alListenerf(AL_GAIN,(ALfloat)_volume);
|
|
al_audio_errcheck("alListerf(AL_GAIN)");*/
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_volume()
|
|
// Access: Public
|
|
// Description:
|
|
// Gets listener gain
|
|
////////////////////////////////////////////////////////////////////
|
|
float OpenALAudioManager::
|
|
get_volume() const {
|
|
return _volume;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::set_play_rate
|
|
// Access: Public
|
|
// Description: set the overall play rate
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
set_play_rate(float play_rate) {
|
|
if (_play_rate!=play_rate) {
|
|
_play_rate = play_rate;
|
|
// Tell our AudioSounds to adjust:
|
|
AllSounds::iterator i=_all_sounds.begin();
|
|
for (; i!=_all_sounds.end(); ++i) {
|
|
(**i).set_play_rate((**i).get_play_rate());
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_play_rate
|
|
// Access: Public
|
|
// Description: get the overall speed/pitch/play rate
|
|
////////////////////////////////////////////////////////////////////
|
|
float OpenALAudioManager::
|
|
get_play_rate() const {
|
|
return _play_rate;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::set_active(bool active)
|
|
// Access: Public
|
|
// Description: Turn on/off
|
|
// Warning: not implemented.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
set_active(bool active) {
|
|
if (_active!=active) {
|
|
_active=active;
|
|
// Tell our AudioSounds to adjust:
|
|
AllSounds::iterator i=_all_sounds.begin();
|
|
for (; i!=_all_sounds.end(); ++i) {
|
|
(**i).set_active(_active);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_active()
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
bool OpenALAudioManager::
|
|
get_active() const {
|
|
return _active;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_set_listener_attributes
|
|
// Access: Public
|
|
// Description: Set position of the "ear" that picks up 3d sounds
|
|
// NOW LISTEN UP!!! THIS IS IMPORTANT!
|
|
// Both Panda3D and OpenAL use a right handed coordinate system.
|
|
// But there is a major difference!
|
|
// In Panda3D the Y-Axis is going into the Screen and the Z-Axis is going up.
|
|
// In OpenAL the Y-Axis is going up and the Z-Axis is coming out of the screen.
|
|
// The solution is simple, we just flip the Y and Z axis and negate the Z, as we move coordinates
|
|
// from Panda to OpenAL and back.
|
|
// What does did mean to average Panda user? Nothing, they shouldn't notice anyway.
|
|
// But if you decide to do any 3D audio work in here you have to keep it in mind.
|
|
// I told you, so you can't say I didn't.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
audio_3d_set_listener_attributes(float px, float py, float pz, float vx, float vy, float vz, float fx, float fy, float fz, float ux, float uy, float uz) {
|
|
_position[0] = px;
|
|
_position[1] = pz;
|
|
_position[2] = -py;
|
|
|
|
_velocity[0] = vx;
|
|
_velocity[1] = vz;
|
|
_velocity[2] = -vy;
|
|
|
|
_forward_up[0] = fx;
|
|
_forward_up[1] = fz;
|
|
_forward_up[2] = -fy;
|
|
|
|
_forward_up[3] = ux;
|
|
_forward_up[4] = uz;
|
|
_forward_up[5] = -uy;
|
|
|
|
|
|
make_current();
|
|
|
|
alGetError(); // clear errors
|
|
alListenerfv(AL_POSITION,_position);
|
|
al_audio_errcheck("alListerfv(AL_POSITION)");
|
|
alListenerfv(AL_VELOCITY,_velocity);
|
|
al_audio_errcheck("alListerfv(AL_VELOCITY)");
|
|
alListenerfv(AL_ORIENTATION,_forward_up);
|
|
al_audio_errcheck("alListerfv(AL_ORIENTATION)");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_get_listener_attributes
|
|
// Access: Public
|
|
// Description: Get position of the "ear" that picks up 3d sounds
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
audio_3d_get_listener_attributes(float *px, float *py, float *pz, float *vx, float *vy, float *vz, float *fx, float *fy, float *fz, float *ux, float *uy, float *uz) {
|
|
*px = _position[0];
|
|
*py = -_position[2];
|
|
*pz = _position[1];
|
|
|
|
*vx = _velocity[0];
|
|
*vy = -_velocity[2];
|
|
*vz = _velocity[1];
|
|
|
|
*fx = _forward_up[0];
|
|
*fy = -_forward_up[2];
|
|
*fz = _forward_up[1];
|
|
|
|
*ux = _forward_up[3];
|
|
*uy = -_forward_up[5];
|
|
*uz = _forward_up[4];
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_set_distance_factor
|
|
// Access: Public
|
|
// Description: Set units per foot
|
|
// WARNING: OpenAL has no distance factor but we use this as a scale
|
|
// on the min/max distances of sounds to preserve FMOD compatibility.
|
|
// Also, adjusts the speed of sound to compensate for unit difference.
|
|
// OpenAL's default speed of sound is 343.3 m/s == 1126.3 ft/s
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
audio_3d_set_distance_factor(float factor) {
|
|
_distance_factor = factor;
|
|
|
|
make_current();
|
|
|
|
alGetError(); // clear errors
|
|
|
|
if (_distance_factor>0) {
|
|
alSpeedOfSound(1126.3*_distance_factor);
|
|
al_audio_errcheck("alSpeedOfSound()");
|
|
// resets the doppler factor to the correct setting in case it was set to 0.0 by a distance_factor<=0.0
|
|
alDopplerFactor(_doppler_factor);
|
|
al_audio_errcheck("alDopplerFactor()");
|
|
} else {
|
|
audio_debug("can't set speed of sound if distance_factor <=0.0, setting doppler factor to 0.0 instead");
|
|
alDopplerFactor(0.0);
|
|
al_audio_errcheck("alDopplerFactor()");
|
|
}
|
|
|
|
AllSounds::iterator i=_all_sounds.begin();
|
|
for (; i!=_all_sounds.end(); ++i) {
|
|
(**i).set_3d_min_distance((**i).get_3d_min_distance());
|
|
(**i).set_3d_max_distance((**i).get_3d_max_distance());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_get_distance_factor
|
|
// Access: Public
|
|
// Description: Sets units per foot
|
|
////////////////////////////////////////////////////////////////////
|
|
float OpenALAudioManager::
|
|
audio_3d_get_distance_factor() const {
|
|
return _distance_factor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_set_doppler_factor
|
|
// Access: Public
|
|
// Description: Exaggerates or diminishes the Doppler effect.
|
|
// Defaults to 1.0
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
audio_3d_set_doppler_factor(float factor) {
|
|
_doppler_factor = factor;
|
|
|
|
make_current();
|
|
|
|
alGetError(); // clear errors
|
|
alDopplerFactor(_doppler_factor);
|
|
al_audio_errcheck("alDopplerFactor()");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_get_doppler_factor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
float OpenALAudioManager::
|
|
audio_3d_get_doppler_factor() const {
|
|
return _doppler_factor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_set_drop_off_factor
|
|
// Access: Public
|
|
// Description: Control the effect distance has on audability.
|
|
// Defaults to 1.0
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
audio_3d_set_drop_off_factor(float factor) {
|
|
_drop_off_factor = factor;
|
|
|
|
AllSounds::iterator i=_all_sounds.begin();
|
|
for (; i!=_all_sounds.end(); ++i) {
|
|
(**i).set_3d_drop_off_factor((**i).get_3d_drop_off_factor());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::audio_3d_get_drop_off_factor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
float OpenALAudioManager::
|
|
audio_3d_get_drop_off_factor() const {
|
|
return _drop_off_factor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::starting_sound
|
|
// Access:
|
|
// Description: Inform the manager that a sound is about to play.
|
|
// The manager will add this sound to the table of
|
|
// sounds that are playing, and will allocate a source
|
|
// to this sound.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
starting_sound(OpenALAudioSound* audio) {
|
|
ALuint source=0;
|
|
|
|
// If the sound already has a source, we don't need to do anything.
|
|
if (audio->_source) {
|
|
return;
|
|
}
|
|
|
|
// first give all sounds that have finished a chance to stop, so that these get stopped first
|
|
update();
|
|
|
|
if (_concurrent_sound_limit) {
|
|
reduce_sounds_playing_to(_concurrent_sound_limit-1); // because we're about to add one
|
|
}
|
|
|
|
// get a source from the source pool or create a new source
|
|
if (_al_sources->empty()) {
|
|
make_current();
|
|
alGetError(); // clear errors
|
|
alGenSources(1,&source);
|
|
ALenum result = alGetError();
|
|
if (result!=AL_NO_ERROR) {
|
|
audio_error("alGenSources(): " << alGetString(result) );
|
|
// if we can't create any more sources, set stop a sound to free a source
|
|
reduce_sounds_playing_to(_sounds_playing.size()-1);
|
|
source = 0;
|
|
}
|
|
}
|
|
// get a source from the source bool if we didn't just allocate one
|
|
if (!source && !_al_sources->empty()) {
|
|
source = *(_al_sources->begin());
|
|
_al_sources->erase(source);
|
|
}
|
|
|
|
audio->_source = source;
|
|
|
|
if (source)
|
|
_sounds_playing.insert(audio);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::stopping_sound
|
|
// Access:
|
|
// Description: Inform the manager that a sound is finished or
|
|
// someone called stop on the sound (this should not
|
|
// be called if a sound is only paused).
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
stopping_sound(OpenALAudioSound* audio) {
|
|
if (audio->_source) {
|
|
_al_sources->insert(audio->_source);
|
|
audio->_source = 0;
|
|
}
|
|
_sounds_playing.erase(audio); // This could cause the sound to destruct.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::set_concurrent_sound_limit
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
set_concurrent_sound_limit(unsigned int limit) {
|
|
_concurrent_sound_limit = limit;
|
|
reduce_sounds_playing_to(_concurrent_sound_limit);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::get_concurrent_sound_limit
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
unsigned int OpenALAudioManager::
|
|
get_concurrent_sound_limit() const {
|
|
return _concurrent_sound_limit;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::reduce_sounds_playing_to
|
|
// Access: Private
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
reduce_sounds_playing_to(unsigned int count) {
|
|
// first give all sounds that have finished a chance to stop, so that these get stopped first
|
|
update();
|
|
|
|
int limit = _sounds_playing.size() - count;
|
|
while (limit-- > 0) {
|
|
SoundsPlaying::iterator sound = _sounds_playing.begin();
|
|
assert(sound != _sounds_playing.end());
|
|
// When the user stops a sound, there is still a PT in the
|
|
// user's hand. When we stop a sound here, however,
|
|
// this can remove the last PT. This can cause an ugly
|
|
// recursion where stop calls the destructor, and the
|
|
// destructor calls stop. To avoid this, we create
|
|
// a temporary PT, stop the sound, and then release the PT.
|
|
PT(OpenALAudioSound) s = (*sound);
|
|
s->stop();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::stop_all_sounds()
|
|
// Access: Public
|
|
// Description: Stop playback on all sounds managed by this manager.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
stop_all_sounds() {
|
|
reduce_sounds_playing_to(0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::update
|
|
// Access: Public
|
|
// Description: Perform all per-frame update functions.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
update() {
|
|
|
|
// See if any of our playing sounds have ended
|
|
// we must first collect a seperate list of finished sounds and then
|
|
// iterated over those again calling their finished method. We
|
|
// can't call finished() within a loop iterating over _sounds_playing
|
|
// since finished() modifies _sounds_playing
|
|
SoundsPlaying sounds_finished;
|
|
|
|
double rtc = TrueClock::get_global_ptr()->get_short_time();
|
|
SoundsPlaying::iterator i=_sounds_playing.begin();
|
|
for (; i!=_sounds_playing.end(); ++i) {
|
|
OpenALAudioSound *sound = (*i);
|
|
sound->pull_used_buffers();
|
|
sound->push_fresh_buffers();
|
|
sound->restart_stalled_audio();
|
|
sound->cache_time(rtc);
|
|
if ((sound->_source == 0)||
|
|
((sound->_stream_queued.size() == 0)&&
|
|
(sound->_loops_completed >= sound->_playing_loops))) {
|
|
sounds_finished.insert(*i);
|
|
}
|
|
}
|
|
|
|
i=sounds_finished.begin();
|
|
for (; i!=sounds_finished.end(); ++i) {
|
|
(**i).finished();
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::cleanup
|
|
// Access: Private
|
|
// Description: Shuts down the audio manager and releases any
|
|
// resources associated with it. Also cleans up all
|
|
// AudioSounds created via the manager.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
cleanup() {
|
|
if (!_cleanup_required) {
|
|
return;
|
|
}
|
|
|
|
stop_all_sounds();
|
|
|
|
AllSounds sounds(_all_sounds);
|
|
AllSounds::iterator ai;
|
|
for (ai = sounds.begin(); ai != sounds.end(); ++ai) {
|
|
(*ai)->cleanup();
|
|
}
|
|
|
|
clear_cache();
|
|
|
|
nassertv(_active_managers > 0);
|
|
--_active_managers;
|
|
|
|
if (_active_managers == 0) {
|
|
if (_openal_active) {
|
|
// empty the source cache
|
|
int i=0;
|
|
ALuint *sources;
|
|
sources = new ALuint[_al_sources->size()];
|
|
for (SourceCache::iterator si = _al_sources->begin(); si!=_al_sources->end(); ++si) {
|
|
sources[i++]=*si;
|
|
}
|
|
make_current();
|
|
alGetError(); // clear errors
|
|
alDeleteSources(_al_sources->size(),sources);
|
|
al_audio_errcheck("alDeleteSources()");
|
|
delete [] sources;
|
|
_al_sources->clear();
|
|
|
|
// make sure that the context is not current when it is destroyed
|
|
alcGetError(_device); // clear errors
|
|
alcMakeContextCurrent(NULL);
|
|
alc_audio_errcheck("alcMakeContextCurrent(NULL)",_device);
|
|
|
|
alcDestroyContext(_context);
|
|
alc_audio_errcheck("alcDestroyContext(_context)",_device);
|
|
_context = NULL;
|
|
|
|
if (_device) {
|
|
audio_debug("Going to try to close openAL");
|
|
alcCloseDevice(_device);
|
|
//alc_audio_errcheck("alcCloseDevice(_device)",_device);
|
|
_device = NULL;
|
|
audio_debug("openAL Closed");
|
|
}
|
|
|
|
_openal_active = false;
|
|
}
|
|
}
|
|
_cleanup_required = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::SoundData::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
OpenALAudioManager::SoundData::
|
|
SoundData() :
|
|
_manager(0),
|
|
_movie(0),
|
|
_sample(0),
|
|
_stream(NULL),
|
|
_length(0.0),
|
|
_rate(0),
|
|
_channels(0),
|
|
_client_count(0)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::SoundData::Destructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
OpenALAudioManager::SoundData::
|
|
~SoundData() {
|
|
if (_sample != 0) {
|
|
if (_manager->_is_valid) {
|
|
_manager->make_current();
|
|
alDeleteBuffers(1,&_sample);
|
|
}
|
|
_sample = 0;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::increment_client_count
|
|
// Access: Public
|
|
// Description: Increments the SoundData's client count. Any
|
|
// SoundData that is actively in use (ie, has a client)
|
|
// is removed entirely from the expiration queue.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
increment_client_count(SoundData *sd) {
|
|
sd->_client_count += 1;
|
|
audio_debug("Incrementing: " << sd->_movie->get_filename().get_basename() << " " << sd->_client_count);
|
|
if (sd->_client_count == 1) {
|
|
if (sd->_sample) {
|
|
_expiring_samples.erase(sd->_expire);
|
|
} else {
|
|
_expiring_streams.erase(sd->_expire);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::decrement_client_count
|
|
// Access: Public
|
|
// Description: Decrements the SoundData's client count. Sounds
|
|
// that are no longer in use (ie, have no clients)
|
|
// go into the expiration queue. When the expiration
|
|
// queue reaches the cache limit, the first item on
|
|
// the queue is freed.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
decrement_client_count(SoundData *sd) {
|
|
sd->_client_count -= 1;
|
|
audio_debug("Decrementing: " << sd->_movie->get_filename().get_basename() << " " << sd->_client_count);
|
|
if (sd->_client_count == 0) {
|
|
if (sd->_sample) {
|
|
_expiring_samples.push_back(sd);
|
|
sd->_expire = _expiring_samples.end();
|
|
sd->_expire--;
|
|
} else {
|
|
_expiring_streams.push_back(sd);
|
|
sd->_expire = _expiring_streams.end();
|
|
sd->_expire--;
|
|
}
|
|
discard_excess_cache(_cache_limit);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: OpenALAudioManager::discard_excess_cache
|
|
// Access: Public
|
|
// Description: Discards sounds from the sound cache until the
|
|
// number of sounds remaining is under the limit.
|
|
////////////////////////////////////////////////////////////////////
|
|
void OpenALAudioManager::
|
|
discard_excess_cache(int sample_limit) {
|
|
int stream_limit = 5;
|
|
|
|
while (((int)_expiring_samples.size()) > sample_limit) {
|
|
SoundData *sd = (SoundData*)(_expiring_samples.front());
|
|
assert(sd->_client_count == 0);
|
|
assert(sd->_expire == _expiring_samples.begin());
|
|
_expiring_samples.pop_front();
|
|
_sample_cache.erase(_sample_cache.find(sd->_movie->get_filename()));
|
|
audio_debug("Expiring: " << sd->_movie->get_filename().get_basename());
|
|
delete sd;
|
|
}
|
|
|
|
while (((int)_expiring_streams.size()) > stream_limit) {
|
|
SoundData *sd = (SoundData*)(_expiring_streams.front());
|
|
assert(sd->_client_count == 0);
|
|
assert(sd->_expire == _expiring_streams.begin());
|
|
_expiring_streams.pop_front();
|
|
audio_debug("Expiring: " << sd->_movie->get_filename().get_basename());
|
|
delete sd;
|
|
}
|
|
}
|
|
|
|
#endif //]
|