From 66802e1935da49c7169e7da0fc341d7d8a9fc2aa Mon Sep 17 00:00:00 2001 From: fgenesis Date: Wed, 24 Jul 2013 04:36:36 +0200 Subject: [PATCH] Change AL distance model to AL_NONE, and implement a custom distance attenuation model. This avoid the problems with AL_INVERSE_DISTANCE_CLAMPED (doesn't reach 0), and sounds way better than AL_LINEAR_DISTANCE_CLAMPED, which has an all-too-sudden decay when a sound goes out of audible range. --- BBGE/FmodOpenALBridge.cpp | 221 +++++++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 86 deletions(-) diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index cc6d09d..9a7b105 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -52,6 +52,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef min #undef max +// HACK: global because OpenAL has only one listener anyway +static FMOD_VECTOR s_listenerPos; + /////////////////////////////////////////////////////////////////////////// // Decoder implementation for streamed Ogg Vorbis audio. @@ -88,7 +91,9 @@ public: static void startDecoderThread(); static void stopDecoderThread(); - ALenum getFormat() const { return format; } + int getNumChannels() const { return channels; } + + void setForceMono(bool mono) { forcemono = mono; } private: @@ -119,7 +124,9 @@ private: OggVorbis_File vf; ALenum format; + int channels; int freq; + bool forcemono; bool thread; // true if played by background thread @@ -277,6 +284,9 @@ OggDecoder::OggDecoder(VFILE *fp) this->eof = false; this->samples_done = 0; this->stopped = false; + this->format = 0; + this->channels = 0; + this->forcemono = false; } OggDecoder::OggDecoder(const void *data, long data_size) @@ -296,6 +306,9 @@ OggDecoder::OggDecoder(const void *data, long data_size) this->eof = false; this->samples_done = 0; this->stopped = false; + this->format = 0; + this->channels = 0; + this->forcemono = false; } OggDecoder::~OggDecoder() @@ -338,6 +351,7 @@ bool OggDecoder::start(ALuint source, bool loop) ov_clear(&vf); return false; } + channels = info->channels; if (info->channels == 1) format = AL_FORMAT_MONO16; else if (info->channels == 2) @@ -680,9 +694,9 @@ FMOD_RESULT OpenALSound::release() ALBRIDGE(Sound,getFormat,(FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits),(type, format, channels, bits)) FMOD_RESULT OpenALSound::getFormat(FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits) { - if(channels) - *channels = getNumChannels(); - return FMOD_OK; + if(channels) + *channels = getNumChannels(); + return FMOD_OK; } @@ -710,7 +724,7 @@ public: FMOD_RESULT getMode(FMOD_MODE *mode); void setGroupVolume(const float _volume); - void setDistanceVolume(float _volume); + void setDistanceVolume(float _volume); void setSourceName(const ALuint _sid) { sid = _sid; } ALuint getSourceName() const { return sid; } bool start(OpenALSound *sound); @@ -737,6 +751,9 @@ private: FMOD_CHANNEL_CALLBACK callback; void *userdata; FMOD_MODE _mode; + float mindist; + float maxdist; + bool relative; }; @@ -779,6 +796,9 @@ OpenALChannel::OpenALChannel() , inuse(false) , initial(true) , _mode(FMOD_DEFAULT) + , mindist(0.0f) + , maxdist(0.0f) + , relative(false) { } @@ -793,6 +813,9 @@ void OpenALChannel::reacquire() frequency = 1.0f; sound = NULL; initial = true; + mindist = 0.0f; + maxdist = 0.0f; + relative = false; } void OpenALChannel::setGroupVolume(const float _volume) @@ -822,7 +845,7 @@ bool OpenALChannel::start(OpenALSound *sound) decoder = NULL; return false; } - sound->setNumChannels((decoder->getFormat() == AL_FORMAT_STEREO16) ? 2 : 1); + sound->setNumChannels(decoder->getNumChannels()); } return true; } @@ -952,134 +975,154 @@ FMOD_RESULT OpenALChannel::setPriority(int _priority) ALBRIDGE(Channel,stop,(),()) FMOD_RESULT OpenALChannel::stop() { - if (decoder) - { - decoder->stop(); - decoder = NULL; - } - alSourceStop(sid); - SANITY_CHECK_OPENAL_CALL(); - alSourcei(sid, AL_BUFFER, 0); - SANITY_CHECK_OPENAL_CALL(); - if (sound) - { - sound->release(); - sound = NULL; - } - if (inuse && callback) - callback(this, FMOD_CHANNEL_CALLBACKTYPE_END, NULL, NULL); // FIXME commanddata missing - paused = false; - inuse = false; - initial = false; - return FMOD_OK; + if (decoder) + { + decoder->stop(); + decoder = NULL; + } + alSourceStop(sid); + SANITY_CHECK_OPENAL_CALL(); + alSourcei(sid, AL_BUFFER, 0); + SANITY_CHECK_OPENAL_CALL(); + if (sound) + { + sound->release(); + sound = NULL; + } + if (inuse && callback) + callback(this, FMOD_CHANNEL_CALLBACKTYPE_END, NULL, NULL); // HACK: commanddata missing (but they are not used by the callback) + paused = false; + inuse = false; + initial = false; + return FMOD_OK; } ALBRIDGE(Channel,setCallback,(FMOD_CHANNEL_CALLBACK callback),(callback)) FMOD_RESULT OpenALChannel::setCallback(FMOD_CHANNEL_CALLBACK callback) { - this->callback = callback; - return FMOD_OK; + this->callback = callback; + return FMOD_OK; } ALBRIDGE(Channel,getUserData,(void **userdata),(userdata)) FMOD_RESULT OpenALChannel::getUserData(void **userdata) { - *userdata = this->userdata; - return FMOD_OK; + *userdata = this->userdata; + return FMOD_OK; } ALBRIDGE(Channel,setUserData,(void *userdata),(userdata)) FMOD_RESULT OpenALChannel::setUserData(void *userdata) { - this->userdata = userdata; - return FMOD_OK; + this->userdata = userdata; + return FMOD_OK; } ALBRIDGE(Channel,set3DAttributes,(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel),(pos, vel)) FMOD_RESULT OpenALChannel::set3DAttributes(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel) { - if (pos) - { - alSource3f(sid, AL_POSITION, pos->x, pos->y, pos->z); - int chans = sound->getNumChannels(); - if(sound->getNumChannels() == 1) - setDistanceVolume(1); // nothing to do - else - { - // HACK: reduce volume if far away (OpanAL does not do this for stereo sounds) - // HACK: assume linear distance model - ALfloat listenerPos[3]; - ALint relative = 0; - alGetSourcei(sid, AL_SOURCE_RELATIVE, &relative); - if(relative) - listenerPos[0] = listenerPos[1] = listenerPos[2] = 0; - else - alGetListenerfv(AL_POSITION, &listenerPos[0]); - float dx = listenerPos[0] - pos->x; - float dy = listenerPos[1] - pos->y; - float dz = listenerPos[2] - pos->z; - float distance = sqrtf(dx*dx + dy*dy + dz*dz); - float rolloff = 1, refdist = 0, maxdist = 0; - alGetSourcef(sid, AL_ROLLOFF_FACTOR, &rolloff); - alGetSourcef(sid, AL_REFERENCE_DISTANCE, &refdist); - alGetSourcef(sid, AL_MAX_DISTANCE, &maxdist); - distance = std::max(distance, refdist); - distance = std::min(distance, maxdist); - float gain = (maxdist == refdist) ? 1 : (1 - rolloff * (distance - refdist) / (maxdist - refdist)); - setDistanceVolume(gain); - } + if (pos) + { + alSource3f(sid, AL_POSITION, pos->x, pos->y, pos->z); - } - if(vel) - alSource3f(sid, AL_VELOCITY, vel->x, vel->y, vel->z); + if(maxdist == mindist) + setDistanceVolume(1.0f); + else + { + // This is where custom distance attenuation starts. + float dx, dy, dz; + if(relative) + { + dx = pos->x; + dy = pos->y; + dz = pos->z; + } + else + { + dx = s_listenerPos.x - pos->x; + dy = s_listenerPos.y - pos->y; + dz = s_listenerPos.z - pos->z; + } - SANITY_CHECK_OPENAL_CALL(); + float d2 = dx*dx + dy*dy + dz*dz; - return FMOD_OK; + if(d2 < mindist*mindist) + setDistanceVolume(1.0f); + else if(d2 > maxdist*maxdist) + setDistanceVolume(0.0f); + else + { + // Replacement method for AL_INVERSE_DISTANCE_CLAMPED. + // The problem with this distance model is that the volume never goes down + // to 0, no matter how far sound sources are away from the listener. + // This could be fixed by using AL_LINEAR_DISTANCE_CLAMPED, but this model does not sound + // natural (as the gain/volume/decibels is a logarithmic measure). + // As a remedy, use a simplified quadratic 1D-bezier curve to model + // a decay similar to AL_INVERSE_DISTANCE_CLAMPED, but that actually reaches 0. + // (The formula is simplified, as the control points (1, 0, 0) cause some math to vanish.) -- FG + const float t = ((sqrtf(d2) - mindist) / (maxdist - mindist)); // [0 .. 1] + const float t1 = 1.0f - t; + const float a = t1 * t1; + const float w = 2.0f; // weight; the higher this is, the steeper is the initial falloff, and the slower the final decay before reaching 0 + const float gain = a / (a + (2.0f * w * t * t1) + (t * t)); + setDistanceVolume(gain); + } + } + } + + if(vel) + alSource3f(sid, AL_VELOCITY, vel->x, vel->y, vel->z); + + SANITY_CHECK_OPENAL_CALL(); + + return FMOD_OK; } ALBRIDGE(Channel,set3DMinMaxDistance,(float mindistance, float maxdistance),(mindistance, maxdistance)) FMOD_RESULT OpenALChannel::set3DMinMaxDistance(float mindistance, float maxdistance) { - alSourcef(sid, AL_REFERENCE_DISTANCE, mindistance); - alSourcef(sid, AL_MAX_DISTANCE, maxdistance); - SANITY_CHECK_OPENAL_CALL(); - return FMOD_OK; + alSourcef(sid, AL_REFERENCE_DISTANCE, mindistance); + alSourcef(sid, AL_MAX_DISTANCE, maxdistance); + SANITY_CHECK_OPENAL_CALL(); + mindist = mindistance; + maxdist = maxdistance; + + return FMOD_OK; } ALBRIDGE(Channel,setMode,(FMOD_MODE mode),(mode)) FMOD_RESULT OpenALChannel::setMode(FMOD_MODE mode) { - _mode = mode; - - if(mode & FMOD_3D_HEADRELATIVE) - alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE); - else // FMOD_3D_WORLDRELATIVE is the default according to FMOD docs - alSourcei(sid, AL_SOURCE_RELATIVE, AL_FALSE); + _mode = mode; - SANITY_CHECK_OPENAL_CALL(); + if(mode & FMOD_3D_HEADRELATIVE) + alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE); + else // FMOD_3D_WORLDRELATIVE is the default according to FMOD docs + alSourcei(sid, AL_SOURCE_RELATIVE, AL_FALSE); - return FMOD_OK; + SANITY_CHECK_OPENAL_CALL(); + + return FMOD_OK; } ALBRIDGE(Channel,getMode,(FMOD_MODE *mode),(mode)) FMOD_RESULT OpenALChannel::getMode(FMOD_MODE *mode) { - *mode = _mode; - return FMOD_OK; + *mode = _mode; + return FMOD_OK; } void OpenALChannel::setDistanceVolume(float _volume) { - distvolume = _volume; - applyVolume(); + distvolume = _volume; + applyVolume(); } void OpenALChannel::applyVolume() { - alSourcef(sid, AL_GAIN, volume * groupvolume * distvolume); - SANITY_CHECK_OPENAL_CALL(); + alSourcef(sid, AL_GAIN, volume * groupvolume * distvolume); + SANITY_CHECK_OPENAL_CALL(); } @@ -1568,8 +1611,11 @@ FMOD_RESULT OpenALSystem::init(int maxchannels, const FMOD_INITFLAGS flags, cons // HACK: FMOD doesn't do this. // For completeness, we pass FMOD_3D_LINEARROLLOFF to createSound(). - alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + // We do our own non-standard distance attenuation model, as the modes offered by OpenAL + // are not sufficient. See OpenALChannel::set3DMinMaxDistance() for the gain control code. -- FG + alDistanceModel(AL_NONE); SANITY_CHECK_OPENAL_CALL(); + s_listenerPos.x = s_listenerPos.y = s_listenerPos.z = 0.0f; OggDecoder::startDecoderThread(); @@ -1714,7 +1760,10 @@ FMOD_RESULT OpenALSystem::set3DListenerAttributes(int listener, const FMOD_VECTO } if(pos) + { + s_listenerPos = *pos; alListener3f(AL_POSITION, pos->x, pos->y, pos->z); + } if(vel) alListener3f(AL_VELOCITY, vel->x, vel->y, vel->z);