From e6680da428643ed0678321dabf7607c42d9bd2b2 Mon Sep 17 00:00:00 2001 From: fgenesis Date: Mon, 22 Jul 2013 03:29:57 +0200 Subject: [PATCH 1/7] Implemenet proper positional audio, one issue still. With this patch, entities can serve as a sound source whose position updates relative to the listener. This implements the last missing feature of my previous positional audio patch, which is moving sound sources. Previously, all sounds were relative to the listener with the listener centered at (0, 0, 0), and once a sound started playing, the position could not be changed. Volume was set as an estimation of the distance to the listener. These restrictions are now gone; OpenAL will handle the volume & panning based on the distance. The remaining problem is that stereo sounds are not attenuated, at all. Lua additions: - playSfx() takes an additional parameter now. - entity_setStopSoundsOnDeath() --- Aquaria/Avatar.cpp | 2 +- Aquaria/DSQ.cpp | 52 ++------- Aquaria/DSQ.h | 3 +- Aquaria/Entity.cpp | 16 ++- Aquaria/Entity.h | 7 +- Aquaria/Game.cpp | 4 +- Aquaria/ScriptInterface.cpp | 60 +++++------ BBGE/Core.cpp | 2 + BBGE/FmodOpenALBridge.cpp | 180 ++++++++++++++++++++++++++----- BBGE/FmodOpenALBridge.h | 33 ++++++ BBGE/SoundManager.cpp | 209 ++++++++++++++++++++++++++---------- BBGE/SoundManager.h | 41 ++++++- 12 files changed, 435 insertions(+), 174 deletions(-) diff --git a/Aquaria/Avatar.cpp b/Aquaria/Avatar.cpp index 6e74540..6bf4ad7 100644 --- a/Aquaria/Avatar.cpp +++ b/Aquaria/Avatar.cpp @@ -3466,7 +3466,7 @@ void Avatar::lockToWallCommon() dsq->spawnParticleEffect("LockToWall", position); stopBurst(); stopRoll(); - core->sound->playSfx("LockToWall", 1.0, 0);//, (1000+rand()%100)/1000.0f); + core->sound->playSfx("LockToWall", 1.0); //bursting = false; this->burst = 1; //lastLockToWallPos = position; diff --git a/Aquaria/DSQ.cpp b/Aquaria/DSQ.cpp index ac8f254..1ea2ab6 100644 --- a/Aquaria/DSQ.cpp +++ b/Aquaria/DSQ.cpp @@ -2293,58 +2293,22 @@ void DSQ::playMenuSelectSfx() core->sound->playSfx("MenuSelect"); } -PlaySfx DSQ::calcPositionalSfx(const Vector &position, float maxdist) +void DSQ::playPositionalSfx(const std::string &name, const Vector &position, float f, float fadeOut, SoundHolder *holder) { PlaySfx sfx; - sfx.vol = 0; - if (dsq->game && dsq->game->avatar) - { - Vector diff = position - dsq->game->avatar->position; - - // Aspect-ratio-adjustment: - // Just multiplying the cut-off distance with aspect increases it too much on widescreen, - // so only a part of it is aspect-corrected to make it sound better. - // Aspect is most likely >= 5/4 here, which results in a higher value than - // the default of 1024; this is intended. -- FG - if (maxdist <= 0) - maxdist = 724 + (300 * aspect); - - float dist = diff.getLength2D(); - if (dist < maxdist) - { - sfx.vol = 1.0f - (dist / maxdist); - sfx.pan = (diff.x / maxdist) * 2.0f; - if (sfx.pan < -1) - sfx.pan = -1; - if (sfx.pan > 1) - sfx.pan = 1; - } - } - return sfx; -} - -void DSQ::playPositionalSfx(const std::string &name, const Vector &position, float f, float fadeOut) -{ - PlaySfx sfx = calcPositionalSfx(position); - - // FIXME: Right now, positional sound effects never update their relative position to the - // listener, which means that if they are spawned too far away to be audible, it is not possible - // that they ever get audible at all. Additionally, the current scripting API only provides - // functions to fade sounds OUT, not to set their volume arbitrarily. - // Because audio thread creation is costly, drop sounds that can not be heard. - // This needs to be removed once proper audio source/listener positioning is implemented, - // or the scripting interface gets additional functions to mess with sound. -- FG - if (sfx.vol <= 0) - return; - sfx.freq = f; sfx.name = name; + sfx.relative = false; + sfx.x = position.x; + sfx.y = position.y; void *c = sound->playSfx(sfx); + if (fadeOut != 0) - { sound->fadeSfx(c, SFT_OUT, fadeOut); - } + + if (holder) + holder->linkSound(c); } void DSQ::shutdown() diff --git a/Aquaria/DSQ.h b/Aquaria/DSQ.h index 0360ca3..ed000ff 100644 --- a/Aquaria/DSQ.h +++ b/Aquaria/DSQ.h @@ -1426,8 +1426,7 @@ public: bool voiceOversEnabled; int recentSaveSlot; - PlaySfx calcPositionalSfx(const Vector &position, float maxdist = 0); - void playPositionalSfx(const std::string &name, const Vector &position, float freq=1.0f, float fadeOut=0); + void playPositionalSfx(const std::string &name, const Vector &position, float freq=1.0f, float fadeOut=0, SoundHolder *holder = 0); void playMenuSelectSfx(); diff --git a/Aquaria/Entity.cpp b/Aquaria/Entity.cpp index fda5d24..9bec306 100644 --- a/Aquaria/Entity.cpp +++ b/Aquaria/Entity.cpp @@ -284,6 +284,8 @@ Entity::Entity() setDamageTarget(DT_AVATAR_BUBBLE, false); setDamageTarget(DT_AVATAR_SEED, false); + stopSoundsOnDeath = false; + //debugLog("End Entity::Entity()"); } @@ -573,6 +575,10 @@ void Entity::watchEntity(Entity *e) void Entity::destroy() { + if (stopSoundsOnDeath) + this->stopAllSounds(); + this->unlinkAllSounds(); + /* if (hair) { @@ -1231,6 +1237,8 @@ void Entity::update(float dt) position = backupPos; if (vel.isNan()) vel = backupVel; + + updateSoundPosition(); } void Entity::postUpdate(float dt) @@ -2239,7 +2247,8 @@ void Entity::songNoteDone(int note, float len) void Entity::sound(const std::string &sound, float freq, float fadeOut) { - dsq->playPositionalSfx(sound, position, freq, fadeOut); + dsq->playPositionalSfx(sound, position, freq, fadeOut, this); + updateSoundPosition(); } Vector Entity::getEnergyShotTargetPosition() @@ -3139,3 +3148,8 @@ bool Entity::isEntityInside() } return false; } + +void Entity::updateSoundPosition() +{ + SoundHolder::updateSoundPosition(position.x + offset.x, position.y + offset.y); +} diff --git a/Aquaria/Entity.h b/Aquaria/Entity.h index 71bcf70..a9ae2df 100644 --- a/Aquaria/Entity.h +++ b/Aquaria/Entity.h @@ -196,7 +196,7 @@ enum BounceType BOUNCE_REAL = 1 }; -class Entity : public Quad, public StateMachine +class Entity : public Quad, public StateMachine, public SoundHolder { public: Entity(); @@ -276,6 +276,7 @@ public: void popBubble(); void sound(const std::string &sound, float freq=1, float fadeOut=0); + void setStopSoundsOnDeath(bool stop) { stopSoundsOnDeath = stop; } void freeze(float time); @@ -492,6 +493,8 @@ public: bool isEntityInside(); + void updateSoundPosition(); + protected: bool calledEntityDied; Path *waterBubble; @@ -602,6 +605,8 @@ private: int maxSpeed; int oldMaxSpeed; + bool stopSoundsOnDeath; + }; #endif diff --git a/Aquaria/Game.cpp b/Aquaria/Game.cpp index 69066a1..066d599 100644 --- a/Aquaria/Game.cpp +++ b/Aquaria/Game.cpp @@ -8134,13 +8134,13 @@ void Game::playBurstSound(bool wallJump) int freqBase = 950; if (wallJump) freqBase += 100; - sound->playSfx("Burst", 1, 0);//, (freqBase+rand()%25)/1000.0f); + sound->playSfx("Burst", 1); if (chance(50)) { switch (dsq->continuity.form) { case FORM_BEAST: - sound->playSfx("BeastBurst", (128+rand()%64)/256.0f, 0);//, (freqBase+rand()%25)/1000.0f); + sound->playSfx("BeastBurst", (128+rand()%64)/256.0f); break; } } diff --git a/Aquaria/ScriptInterface.cpp b/Aquaria/ScriptInterface.cpp index 6b57e9c..b795311 100644 --- a/Aquaria/ScriptInterface.cpp +++ b/Aquaria/ScriptInterface.cpp @@ -2763,28 +2763,36 @@ luaFunc(entity_playSfx) void *h = NULL; if (e && !dsq->isSkippingCutscene()) { - PlaySfx sfx = dsq->calcPositionalSfx(e->position, lua_tonumber(L, 7)); + PlaySfx sfx; sfx.name = getString(L, 2); sfx.freq = lua_tonumber(L, 3); - float vol = lua_tonumber(L, 4); + sfx.vol = lua_tonumber(L, 4); sfx.loops = lua_tonumber(L, 5); + float fadeOut = lua_tonumber(L, 6); - if(vol > 0) - sfx.vol *= vol; - - // FIXME: See comment in DSQ::playPositionalSfx() -- FG - if (sfx.vol <= 0) - luaReturnPtr(NULL); + sfx.maxdist = lua_tonumber(L, 7); + sfx.relative = false; h = core->sound->playSfx(sfx); if (fadeOut != 0) { sound->fadeSfx(h, SFT_OUT, fadeOut); } + + e->linkSound(h); + e->updateSoundPosition(); } luaReturnPtr(h); } +luaFunc(entity_setStopSoundsOnDeath) +{ + Entity *e = entity(L); + if (e) + e->setStopSoundsOnDeath(getBool(L, 2)); + luaReturnNil(); +} + luaFunc(entity_setSpiritFreeze) { Entity *e = entity(L); @@ -5927,32 +5935,20 @@ luaFunc(emote) luaFunc(playSfx) { - float freq = lua_tonumber(L, 2); - float vol = lua_tonumber(L, 3); - int loops = lua_tointeger(L, 4); - if (vol <= 0) - vol = 1; - PlaySfx sfx; - - if (lua_isnumber(L, 5) && lua_isnumber(L, 6)) - { - const Vector pos(lua_tonumber(L, 5), lua_tonumber(L, 6)); - sfx = dsq->calcPositionalSfx(pos, lua_tonumber(L, 7)); - sfx.vol *= vol; - } - else - { - sfx.vol = vol; - } - - // FIXME: See comment in DSQ::playPositionalSfx() -- FG - if (sfx.vol <= 0) - luaReturnPtr(NULL); - sfx.name = getString(L, 1); - sfx.freq = freq; - sfx.loops = loops; + sfx.freq = lua_tonumber(L, 2); + sfx.vol = lua_tonumber(L, 3); + if (sfx.vol <= 0) + sfx.vol = 1; + sfx.loops = lua_tointeger(L, 4); + sfx.x = lua_tonumber(L, 5); + sfx.y = lua_tonumber(L, 6); + sfx.maxdist = lua_tonumber(L, 7); + if(lua_isnoneornil(L, 8)) + sfx.relative = (sfx.x == 0 && sfx.y == 0); + else + sfx.relative = getBool(L, 8); void *handle = NULL; diff --git a/BBGE/Core.cpp b/BBGE/Core.cpp index 5593056..09acce9 100644 --- a/BBGE/Core.cpp +++ b/BBGE/Core.cpp @@ -3138,6 +3138,8 @@ void Core::main(float runTime) } } + sound->setListenerPos(screenCenter.x, screenCenter.y); + if (doScreenshot) { if (verbose) debugLog("screenshot"); diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index 42a67ef..47f480e 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -672,7 +672,7 @@ class OpenALChannel { public: OpenALChannel(); - FMOD_RESULT setVolume(const float _volume, const bool setstate=true); + FMOD_RESULT setVolume(const float _volume); FMOD_RESULT setPaused(const bool _paused, const bool setstate=true); FMOD_RESULT setFrequency(const float _frequency); FMOD_RESULT setPriority(int _priority); @@ -682,6 +682,14 @@ public: FMOD_RESULT setChannelGroup(ChannelGroup *channelgroup); FMOD_RESULT stop(); FMOD_RESULT setPan(const float pan); + FMOD_RESULT setCallback(FMOD_CHANNEL_CALLBACK callback); + FMOD_RESULT getUserData(void **userdata); + FMOD_RESULT setUserData(void *userdata); + FMOD_RESULT set3DAttributes(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel); + FMOD_RESULT set3DMinMaxDistance(float mindistance, float maxdistance); + FMOD_RESULT setMode(FMOD_MODE mode); + FMOD_RESULT getMode(FMOD_MODE *mode); + void setGroupVolume(const float _volume); void setSourceName(const ALuint _sid) { sid = _sid; } ALuint getSourceName() const { return sid; } @@ -703,6 +711,9 @@ private: OggDecoder *decoder; bool inuse; bool initial; + FMOD_CHANNEL_CALLBACK callback; + void *userdata; + FMOD_MODE _mode; }; @@ -743,6 +754,7 @@ OpenALChannel::OpenALChannel() , decoder(NULL) , inuse(false) , initial(true) + , _mode(FMOD_DEFAULT) { } @@ -805,10 +817,9 @@ void OpenALChannel::update() } ALBRIDGE(Channel,setVolume,(float volume),(volume)) -FMOD_RESULT OpenALChannel::setVolume(const float _volume, const bool setstate) +FMOD_RESULT OpenALChannel::setVolume(const float _volume) { - if (setstate) - volume = _volume; + volume = _volume; alSourcef(sid, AL_GAIN, _volume * groupvolume); SANITY_CHECK_OPENAL_CALL(); return FMOD_OK; @@ -922,6 +933,98 @@ FMOD_RESULT OpenALChannel::setPan(const float pan) return FMOD_OK; } +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; +} + + +ALBRIDGE(Channel,setCallback,(FMOD_CHANNEL_CALLBACK callback),(callback)) +FMOD_RESULT OpenALChannel::setCallback(FMOD_CHANNEL_CALLBACK callback) +{ + this->callback = callback; + return FMOD_OK; +} + +ALBRIDGE(Channel,getUserData,(void **userdata),(userdata)) +FMOD_RESULT OpenALChannel::getUserData(void **userdata) +{ + *userdata = this->userdata; + return FMOD_OK; +} + +ALBRIDGE(Channel,setUserData,(void *userdata),(userdata)) +FMOD_RESULT OpenALChannel::setUserData(void *userdata) +{ + 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); + 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; +} + +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); + + 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; +} + + // FMOD::ChannelGroup implementation... @@ -1066,29 +1169,6 @@ void OpenALChannel::setSound(OpenALSound *_sound) } -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; - } - paused = false; - inuse = false; - initial = false; - return FMOD_OK; -} - // FMOD::System implementation ... @@ -1112,6 +1192,7 @@ public: FMOD_RESULT getDriverCaps(const int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency, FMOD_SPEAKERMODE *controlpanelspeakermode); FMOD_RESULT getMasterChannelGroup(ChannelGroup **channelgroup); FMOD_RESULT playSound(FMOD_CHANNELINDEX channelid, Sound *sound, bool paused, Channel **channel); + FMOD_RESULT set3DListenerAttributes(int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); FMOD_RESULT getNumChannels(int *maxchannels_ret); @@ -1415,7 +1496,7 @@ FMOD_RESULT OpenALSystem::init(int maxchannels, const FMOD_INITFLAGS flags, cons break; } - alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(sid, AL_SOURCE_RELATIVE, AL_FALSE); SANITY_CHECK_OPENAL_CALL(); alSource3f(sid, AL_POSITION, 0.0f, 0.0f, 0.0f); // no panning or spatialization in Aquaria. SANITY_CHECK_OPENAL_CALL(); @@ -1426,6 +1507,12 @@ FMOD_RESULT OpenALSystem::init(int maxchannels, const FMOD_INITFLAGS flags, cons ss << "Using " << num_channels << " sound channels."; debugLog(ss.str()); + // HACK: FMOD doesn't do this. + // For completeness, we pass FMOD_3D_LINEARROLLOFF to createSound(). + alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + SANITY_CHECK_OPENAL_CALL(); + + OggDecoder::startDecoderThread(); return FMOD_OK; @@ -1539,6 +1626,43 @@ FMOD_RESULT OpenALSystem::update() return FMOD_OK; } +ALBRIDGE(System, set3DListenerAttributes, (int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up), + (listener, pos, vel, forward, up)); +FMOD_RESULT OpenALSystem::set3DListenerAttributes(int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up) +{ + // ignore listener parameter; there is only one listener in OpenAL. + + if(up || forward) + { + ALfloat orientation[6]; + alGetListenerfv(AL_ORIENTATION, &orientation[0]); + + if(forward) + { + orientation[0] = forward->x; + orientation[1] = forward->y; + orientation[2] = forward->z; + } + + if(up) + { + orientation[3] = up->x; + orientation[4] = up->y; + orientation[5] = up->z; + } + + alListenerfv(AL_ORIENTATION, &orientation[0]); + } + + if(pos) + alListener3f(AL_POSITION, pos->x, pos->y, pos->z); + + if(vel) + alListener3f(AL_VELOCITY, vel->x, vel->y, vel->z); + + return FMOD_OK; +} + // misc FMOD bits... diff --git a/BBGE/FmodOpenALBridge.h b/BBGE/FmodOpenALBridge.h index 3da553b..6de856b 100644 --- a/BBGE/FmodOpenALBridge.h +++ b/BBGE/FmodOpenALBridge.h @@ -60,6 +60,24 @@ typedef enum FMOD_DSP_TYPE_REVERB, } FMOD_DSP_TYPE; +typedef enum +{ + FMOD_CHANNEL_CALLBACKTYPE_END, /* Called when a sound ends. */ + FMOD_CHANNEL_CALLBACKTYPE_VIRTUALVOICE, /* Called when a voice is swapped out or swapped in. */ + FMOD_CHANNEL_CALLBACKTYPE_SYNCPOINT, /* Called when a syncpoint is encountered. Can be from wav file markers. */ + FMOD_CHANNEL_CALLBACKTYPE_OCCLUSION, /* Called when the channel has its geometry occlusion value calculated. Can be used to clamp or change the value. */ + + FMOD_CHANNEL_CALLBACKTYPE_MAX, /* Maximum number of callback types supported. */ + FMOD_CHANNEL_CALLBACKTYPE_FORCEINT = 65536 /* Makes sure this enum is signed 32bit. */ +} FMOD_CHANNEL_CALLBACKTYPE; + +typedef struct +{ + float x; /* X co-ordinate in 3D space. */ + float y; /* Y co-ordinate in 3D space. */ + float z; /* Z co-ordinate in 3D space. */ +} FMOD_VECTOR; + #define F_CALLBACK typedef int FMOD_CAPS; @@ -77,8 +95,13 @@ typedef int FMOD_MODE; #define FMOD_LOOP_OFF (1<<4) #define FMOD_LOOP_NORMAL (1<<5) #define FMOD_LOWMEM (1<<6) +#define FMOD_3D (1<<7) +#define FMOD_3D_HEADRELATIVE (1<<8) +#define FMOD_3D_WORLDRELATIVE (1<<9) +#define FMOD_3D_LINEARROLLOFF (1<<10) #define FMOD_DEFAULT (FMOD_2D | FMOD_HARDWARE) + typedef int FMOD_CREATESOUNDEXINFO; #define FMOD_INIT_NORMAL 0 @@ -92,6 +115,8 @@ typedef FMOD_RESULT (*FMOD_FILE_CLOSECALLBACK)(void *,void *); typedef FMOD_RESULT (*FMOD_FILE_READCALLBACK)(void *,void *,unsigned int,unsigned int *,void *); typedef FMOD_RESULT (*FMOD_FILE_SEEKCALLBACK)(void *,unsigned int,void *); +typedef FMOD_RESULT (*FMOD_CHANNEL_CALLBACK)(void *channel, FMOD_CHANNEL_CALLBACKTYPE type, void *commanddata1, void *commanddata2); + typedef enum { @@ -146,6 +171,13 @@ namespace FMOD FMOD_RESULT stop(); FMOD_RESULT setPaused(bool paused); FMOD_RESULT setPan(float pan); + FMOD_RESULT setCallback(FMOD_CHANNEL_CALLBACK callback); + FMOD_RESULT getUserData(void **userdata); + FMOD_RESULT setUserData(void *userdata); + FMOD_RESULT set3DAttributes(const FMOD_VECTOR *pos, const FMOD_VECTOR *vel); + FMOD_RESULT set3DMinMaxDistance(float mindistance, float maxdistance); + FMOD_RESULT setMode(FMOD_MODE mode); + FMOD_RESULT getMode(FMOD_MODE *mode); }; class System @@ -165,6 +197,7 @@ namespace FMOD FMOD_RESULT setFileSystem(FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, int blockalign); FMOD_RESULT setSpeakerMode(FMOD_SPEAKERMODE speakermode); FMOD_RESULT update(); + FMOD_RESULT set3DListenerAttributes(int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up); // BBGE-specific... FMOD_RESULT getNumChannels(int *maxchannels_ret); diff --git a/BBGE/SoundManager.cpp b/BBGE/SoundManager.cpp index 4dfa57c..621c83b 100644 --- a/BBGE/SoundManager.cpp +++ b/BBGE/SoundManager.cpp @@ -1057,6 +1057,11 @@ bool SoundManager::playVoice(const std::string &name, SoundVoiceType svt, float voiceChannel->setPan(0); voiceChannel->setFrequency(1); + voiceChannel->setCallback(NULL); + voiceChannel->setUserData(NULL); + voiceChannel->set3DMinMaxDistance(0.0f, 0.0f); + setSoundPos(voiceChannel, 0, 0); + setSoundRelative(voiceChannel, true); result = voiceChannel->setPaused(false); checkError(); @@ -1143,13 +1148,32 @@ void *SoundManager::playSfx(const PlaySfx &play) checkError(); } - channel->setPan(play.pan); + //channel->setPan(play.pan); float freq = play.freq; if (freq <= 0) freq = 1; channel->setFrequency(freq); + channel->setCallback(NULL); + channel->setUserData(NULL); + + // position in space + setSoundPos(channel, play.x, play.y); + setSoundRelative(channel, play.relative); + + // distance gain attenuation: stereo separation + silence at further away than maxdist + float maxdist = play.maxdist; + if (!maxdist) + maxdist = 1300; + + if(maxdist > 0) + channel->set3DMinMaxDistance(maxdist * 0.3, maxdist); // HACK: this works reasonably well + else + channel->set3DMinMaxDistance(0, 0); // no attenuation + + + result = channel->setPaused(false); checkError(); @@ -1160,23 +1184,11 @@ void *SoundManager::playSfx(const PlaySfx &play) return 0; } -void *SoundManager::playSfx(const std::string &name, float vol, float pan, float freq) +void *SoundManager::playSfx(const std::string &name, float vol) { PlaySfx play; play.name = name; play.vol = vol; - play.pan = pan; - play.freq = freq; - return playSfx(play); -} - -void *SoundManager::playSfx(int handle, float vol, float pan, float freq) -{ - PlaySfx play; - play.handle = handle; - play.vol = vol; - play.pan = pan; - play.freq = freq; return playSfx(play); } @@ -1205,49 +1217,6 @@ bool SoundManager::isPlayingMusic(const std::string &name) return false; } -bool SoundManager::playMod(const std::string &name) -{ - std::string fn; - - fn = musicPath + name; - stringToLower(fn); - - FMOD_MODE mode=0; - - //FMOD_CREATESOUNDEXINFO exinfo; - - //mode = FMOD_2D | FMOD_SOFTWARE | FMOD_CREATESAMPLE;//FMOD_CREATESTREAM; - mode = FMOD_HARDWARE | FMOD_2D | FMOD_CREATESTREAM; - - debugLog("createSound: " + fn); - result = SoundCore::system->createSound(fn.c_str(), mode, 0, &modSound); - if (checkError()) - { - debugLog("createSound failed"); - return false; - } - - debugLog("playSound"); - result = SoundCore::system->playSound(FMOD_CHANNEL_FREE, modSound, false, &modChannel); - checkError(); - - debugLog("setChannelGroup"); - result = modChannel->setChannelGroup(group_mus); - checkError(); - - debugLog("setPriority"); - result = modChannel->setPriority(0); // should be highest priority (according to the docs) - checkError(); - - debugLog("setPaused"); - result = modChannel->setPaused(false); - checkError(); - - debugLog("returning"); - - return true; -} - bool SoundManager::playMusic(const std::string &name, SoundLoopType slt, SoundFadeType sft, float trans, SoundConditionType sct) { debugLog("playMusic: " + name); @@ -1373,6 +1342,11 @@ bool SoundManager::playMusic(const std::string &name, SoundLoopType slt, SoundFa musicChannel->setFrequency(1); // in case the channel was used by a pitch-shifted sound before musicChannel->setPan(0); + musicChannel->setCallback(NULL); + musicChannel->setUserData(NULL); + musicChannel->set3DMinMaxDistance(0.0f, 0.0f); // disable attenuation // FIXME: is that right? + setSoundPos(musicChannel, 0, 0); + setSoundRelative(musicChannel, true); result = musicChannel->setPaused(false); // This is where the sound really starts. checkError(); @@ -1557,7 +1531,7 @@ Buffer SoundManager::loadSoundIntoBank(const std::string &filename, const std::s if (sound) return sound; - FMOD_MODE mode = FMOD_DEFAULT | FMOD_LOWMEM; + FMOD_MODE mode = FMOD_DEFAULT | FMOD_LOWMEM | FMOD_3D | FMOD_3D_LINEARROLLOFF; if (loop) mode |= FMOD_LOOP_NORMAL; @@ -1680,3 +1654,122 @@ bool SoundManager::checkError() #endif return false; } + +void SoundManager::setListenerPos(float x, float y) +{ + FMOD_VECTOR pos; + pos.x = x; + pos.y = y; + pos.z = 0.0f; + + FMOD_VECTOR forward; + forward.x = 0.0f; + forward.y = 0.0f; + forward.z = -1.0f; + + FMOD_VECTOR up; + up.x = 0.0f; + up.y = 1.0f; + up.z = 0.0f; + + SoundCore::system->set3DListenerAttributes(0, &pos, NULL, &forward, &up); +} + +void SoundManager::setSoundPos(void *channel, float x, float y) +{ + if (!channel) + return; + + FMOD::Channel *pChannel = (FMOD::Channel*)channel; + FMOD_VECTOR pos; + pos.x = x; + pos.y = y; + pos.z = 0.0f; + + pChannel->set3DAttributes(&pos, NULL); +} + +void SoundManager::setSoundRelative(void *channel, bool relative) +{ + if (!channel) + return; + + FMOD::Channel *pChannel = (FMOD::Channel*)channel; + + FMOD_MODE mode = 0; + pChannel->getMode(&mode); + FMOD_MODE newmode = mode & ~(FMOD_3D_WORLDRELATIVE | FMOD_3D_HEADRELATIVE); + if(relative) + newmode |= (FMOD_3D_HEADRELATIVE | FMOD_3D); + else + newmode |= (FMOD_3D_WORLDRELATIVE | FMOD_3D); + + if (mode != newmode) + pChannel->setMode(newmode); +} + +static FMOD_RESULT s_soundHolderCallback(void *channel, FMOD_CHANNEL_CALLBACKTYPE type, void *commanddata1, void *commanddata2) +{ + if (!channel) + return FMOD_ERR_INVALID_PARAM; + + SoundHolder *holder = NULL; + FMOD::Channel *pChannel = (FMOD::Channel*)channel; + pChannel->getUserData((void**)&holder); + + switch(type) + { + case FMOD_CHANNEL_CALLBACKTYPE_END: + holder->unlinkSound(channel); + break; + default: + return FMOD_ERR_INVALID_PARAM; + } + + return FMOD_OK; +} + + +SoundHolder::~SoundHolder() +{ + unlinkAllSounds(); +} + +void SoundHolder::updateSoundPosition(float x, float y) +{ + if (activeSounds.size()) + for(std::set::iterator it = activeSounds.begin(); it != activeSounds.end(); ++it) + sound->setSoundPos(*it, x, y); +} + +void SoundHolder::stopAllSounds() +{ + // activeSounds is modified by SoundManager::stopSfx(), which calls unkinkSound(), can't use iterator here + while(activeSounds.size()) + sound->stopSfx(*activeSounds.begin()); +} + +void SoundHolder::unlinkSound(void *channel) +{ + FMOD::Channel *pChannel = (FMOD::Channel*)channel; + pChannel->setUserData(NULL); + pChannel->setCallback(NULL); + activeSounds.erase(channel); +} + +void SoundHolder::linkSound(void *channel) +{ + if (!channel) + return; + FMOD::Channel *pChannel = (FMOD::Channel*)channel; + pChannel->setUserData(this); + pChannel->setCallback(s_soundHolderCallback); + activeSounds.insert(channel); +} + +void SoundHolder::unlinkAllSounds() +{ + while(activeSounds.size()) + unlinkSound(*activeSounds.begin()); +} + diff --git a/BBGE/SoundManager.h b/BBGE/SoundManager.h index eb9646f..cd18d65 100644 --- a/BBGE/SoundManager.h +++ b/BBGE/SoundManager.h @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include +#include #include "Vector.h" // if using SDL_MIXER @@ -103,20 +104,26 @@ enum SoundLoadType struct PlaySfx { - PlaySfx() : priority(0.5), handle(0), pan(0), vol(1), fade(SFT_NONE), time(0), freq(1), loops(0), channel(BBGE_AUDIO_NOCHANNEL) {} + PlaySfx() : priority(0.5), handle(0), vol(1), fade(SFT_NONE), + time(0), freq(1), loops(0), channel(BBGE_AUDIO_NOCHANNEL), + maxdist(0), x(0), y(0), relative(true) {} std::string name; intptr_t handle; - float pan; float vol; float time; float freq; int loops; int channel; float priority; + float maxdist; // distance gain attenuation. if 0: use default value, -1: don't attenuate at all SoundFadeType fade; + float x, y; + bool relative; }; +class SoundHolder; // defined below + class SoundManager { public: @@ -140,16 +147,18 @@ public: SoundCore::Buffer getBuffer(const std::string &name); void *playSfx(const PlaySfx &play); - void *playSfx(const std::string &name, float vol=1, float pan=0, float freq=1); - void *playSfx(int handle, float vol=1, float pan=0, float freq=1); + void *playSfx(const std::string &name, float vol=1); - bool playMod(const std::string &name); bool playMusic(const std::string &name, SoundLoopType=SLT_NORMAL, SoundFadeType sft=SFT_NONE, float trans=0, SoundConditionType sct=SCT_NORMAL); bool playVoice(const std::string &name, SoundVoiceType=SVT_QUEUE, float vmod=-1); float getMusicFader(); float getVoxFader(); + void setListenerPos(float x, float y); + void setSoundPos(void *channel, float x, float y); + void setSoundRelative(void *channel, bool relative); + std::string getVolumeString(); void toggleEffectMusic(SoundEffectType effect, bool on); @@ -248,6 +257,28 @@ private: void (*loadProgressCallback)(); }; +class SoundHolder +{ + friend class SoundManager; +public: + void updateSoundPosition(float x, float y); + void stopAllSounds(); + void unlinkSound(void *channel); + void linkSound(void *channel); + void unlinkAllSounds(); + +protected: + virtual ~SoundHolder(); + +private: + std::set activeSounds; +}; + + extern SoundManager *sound; + + + + #endif From f5da61fe78bda47c6fd092beef8308f68938fa21 Mon Sep 17 00:00:00 2001 From: fgenesis Date: Mon, 22 Jul 2013 05:35:50 +0200 Subject: [PATCH 2/7] Horrible hack to attenuate stereo sources properly. OpenAL refuses to pan stereo sources, so apparently it needs to be done this way. --- Aquaria/ScriptInterface.cpp | 2 + BBGE/FmodOpenALBridge.cpp | 76 +++++++++++++++++++++++++++++++++++-- BBGE/FmodOpenALBridge.h | 3 ++ BBGE/SoundManager.cpp | 14 +++---- 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/Aquaria/ScriptInterface.cpp b/Aquaria/ScriptInterface.cpp index b795311..8749efa 100644 --- a/Aquaria/ScriptInterface.cpp +++ b/Aquaria/ScriptInterface.cpp @@ -2767,6 +2767,8 @@ luaFunc(entity_playSfx) sfx.name = getString(L, 2); sfx.freq = lua_tonumber(L, 3); sfx.vol = lua_tonumber(L, 4); + if(sfx.vol <= 0) + sfx.vol = 1; sfx.loops = lua_tonumber(L, 5); float fadeOut = lua_tonumber(L, 6); diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index 47f480e..57e5d4b 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -49,6 +49,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //#define _DEBUG 1 #endif +#undef min +#undef max + /////////////////////////////////////////////////////////////////////////// // Decoder implementation for streamed Ogg Vorbis audio. @@ -85,6 +88,8 @@ public: static void startDecoderThread(); static void stopDecoderThread(); + ALenum getFormat() const { return format; } + private: void _stop(); @@ -597,8 +602,11 @@ public: bool isLooping() const { return looping; } bool isRaw() const { return raw; } FMOD_RESULT release(); + FMOD_RESULT getFormat(FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits); void reference() { refcount++; } ALuint getBufferName() const { return bid; } + int getNumChannels() const { return numChannels; } + void setNumChannels(int c) { numChannels = c; } private: VFILE * const fp; @@ -608,6 +616,7 @@ private: int refcount; const bool raw; // true if buffer holds raw PCM data ALuint bid; // only used if raw == true + int numChannels; }; OpenALSound::OpenALSound(VFILE *_fp, const bool _looping) @@ -618,6 +627,7 @@ OpenALSound::OpenALSound(VFILE *_fp, const bool _looping) , refcount(1) , raw(false) , bid(0) + , numChannels(0) { } @@ -629,6 +639,7 @@ OpenALSound::OpenALSound(void *_data, long _size, const bool _looping) , refcount(1) , raw(false) , bid(0) + , numChannels(0) { } @@ -640,6 +651,7 @@ OpenALSound::OpenALSound(ALuint _bid, const bool _looping) , refcount(1) , raw(true) , bid(_bid) + , numChannels(0) { } @@ -665,6 +677,14 @@ FMOD_RESULT OpenALSound::release() return FMOD_OK; } +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; +} + class OpenALChannelGroup; @@ -691,6 +711,7 @@ public: FMOD_RESULT getMode(FMOD_MODE *mode); void setGroupVolume(const float _volume); + void setDistanceVolume(float _volume); void setSourceName(const ALuint _sid) { sid = _sid; } ALuint getSourceName() const { return sid; } bool start(OpenALSound *sound); @@ -700,9 +721,12 @@ public: void setSound(OpenALSound *sound); private: + void applyVolume(); + ALuint sid; // source id. float groupvolume; float volume; + float distvolume; bool paused; int priority; float frequency; @@ -746,6 +770,7 @@ OpenALChannel::OpenALChannel() : sid(0) , groupvolume(1.0f) , volume(1.0f) + , distvolume(1.0f) , paused(false) , priority(0) , frequency(1.0f) @@ -763,6 +788,7 @@ void OpenALChannel::reacquire() assert(!inuse); inuse = true; volume = 1.0f; + distvolume = 1.0f; paused = true; priority = 0; frequency = 1.0f; @@ -773,8 +799,7 @@ void OpenALChannel::reacquire() void OpenALChannel::setGroupVolume(const float _volume) { groupvolume = _volume; - alSourcef(sid, AL_GAIN, volume * groupvolume); - SANITY_CHECK_OPENAL_CALL(); + applyVolume(); } bool OpenALChannel::start(OpenALSound *sound) @@ -798,6 +823,7 @@ bool OpenALChannel::start(OpenALSound *sound) decoder = NULL; return false; } + sound->setNumChannels((decoder->getFormat() == AL_FORMAT_STEREO16) ? 2 : 1); } return true; } @@ -820,8 +846,7 @@ ALBRIDGE(Channel,setVolume,(float volume),(volume)) FMOD_RESULT OpenALChannel::setVolume(const float _volume) { volume = _volume; - alSourcef(sid, AL_GAIN, _volume * groupvolume); - SANITY_CHECK_OPENAL_CALL(); + applyVolume(); return FMOD_OK; } @@ -984,7 +1009,37 @@ ALBRIDGE(Channel,set3DAttributes,(const FMOD_VECTOR *pos, const FMOD_VECTOR *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(vel) alSource3f(sid, AL_VELOCITY, vel->x, vel->y, vel->z); @@ -1024,6 +1079,18 @@ FMOD_RESULT OpenALChannel::getMode(FMOD_MODE *mode) return FMOD_OK; } +void OpenALChannel::setDistanceVolume(float _volume) +{ + distvolume = _volume; + applyVolume(); +} + +void OpenALChannel::applyVolume() +{ + alSourcef(sid, AL_GAIN, volume * groupvolume * distvolume); + SANITY_CHECK_OPENAL_CALL(); +} + // FMOD::ChannelGroup implementation... @@ -1330,6 +1397,7 @@ FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE { alBufferData(bid, format, data, size, freq); *sound = (Sound *) new OpenALSound(bid, (((mode & FMOD_LOOP_OFF) == 0) && (mode & FMOD_LOOP_NORMAL))); + ((OpenALSound*)*sound)->setNumChannels(format == AL_FORMAT_STEREO16 ? 2 : 1); retval = FMOD_OK; } free(data); diff --git a/BBGE/FmodOpenALBridge.h b/BBGE/FmodOpenALBridge.h index 6de856b..0b1f28e 100644 --- a/BBGE/FmodOpenALBridge.h +++ b/BBGE/FmodOpenALBridge.h @@ -101,6 +101,8 @@ typedef int FMOD_MODE; #define FMOD_3D_LINEARROLLOFF (1<<10) #define FMOD_DEFAULT (FMOD_2D | FMOD_HARDWARE) +typedef int FMOD_SOUND_FORMAT; +typedef int FMOD_SOUND_TYPE; typedef int FMOD_CREATESOUNDEXINFO; @@ -137,6 +139,7 @@ namespace FMOD { public: FMOD_RESULT release(); + FMOD_RESULT getFormat(FMOD_SOUND_TYPE *type, FMOD_SOUND_FORMAT *format, int *channels, int *bits); // only *channels implemented }; class DSP diff --git a/BBGE/SoundManager.cpp b/BBGE/SoundManager.cpp index 621c83b..a3de5a2 100644 --- a/BBGE/SoundManager.cpp +++ b/BBGE/SoundManager.cpp @@ -1060,8 +1060,8 @@ bool SoundManager::playVoice(const std::string &name, SoundVoiceType svt, float voiceChannel->setCallback(NULL); voiceChannel->setUserData(NULL); voiceChannel->set3DMinMaxDistance(0.0f, 0.0f); - setSoundPos(voiceChannel, 0, 0); setSoundRelative(voiceChannel, true); + setSoundPos(voiceChannel, 0, 0); result = voiceChannel->setPaused(false); checkError(); @@ -1158,10 +1158,6 @@ void *SoundManager::playSfx(const PlaySfx &play) channel->setCallback(NULL); channel->setUserData(NULL); - // position in space - setSoundPos(channel, play.x, play.y); - setSoundRelative(channel, play.relative); - // distance gain attenuation: stereo separation + silence at further away than maxdist float maxdist = play.maxdist; if (!maxdist) @@ -1172,6 +1168,10 @@ void *SoundManager::playSfx(const PlaySfx &play) else channel->set3DMinMaxDistance(0, 0); // no attenuation + // position in space + setSoundRelative(channel, play.relative); + setSoundPos(channel, play.x, play.y); // must be set after everything else (See hack in OpenALChannel::set3DAttributes()) + result = channel->setPaused(false); @@ -1344,9 +1344,9 @@ bool SoundManager::playMusic(const std::string &name, SoundLoopType slt, SoundFa musicChannel->setPan(0); musicChannel->setCallback(NULL); musicChannel->setUserData(NULL); - musicChannel->set3DMinMaxDistance(0.0f, 0.0f); // disable attenuation // FIXME: is that right? - setSoundPos(musicChannel, 0, 0); + musicChannel->set3DMinMaxDistance(0.0f, 0.0f); // disable attenuation setSoundRelative(musicChannel, true); + setSoundPos(musicChannel, 0, 0); result = musicChannel->setPaused(false); // This is where the sound really starts. checkError(); From 55bc002cc5582490fa51017fd1b594fcd0315b0f Mon Sep 17 00:00:00 2001 From: fgenesis Date: Mon, 22 Jul 2013 05:36:01 +0200 Subject: [PATCH 3/7] Leftover win32 junk --- BBGE/Base.h | 1 + BBGE/Localization.cpp | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/BBGE/Base.h b/BBGE/Base.h index 31dd920..edb6006 100644 --- a/BBGE/Base.h +++ b/BBGE/Base.h @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef BBGE_BUILD_WINDOWS #define WIN32_LEAN_AND_MEAN + #define WIN32_NOMINMAX #include #ifdef _MSC_VER diff --git a/BBGE/Localization.cpp b/BBGE/Localization.cpp index 4669814..24c5417 100644 --- a/BBGE/Localization.cpp +++ b/BBGE/Localization.cpp @@ -1,10 +1,5 @@ #include "Localization.h" -#ifdef BBGE_BUILD_WINDOWS -#define WIN32_LEAN_AND_MEAN -#include -#endif - #ifdef BBGE_BUILD_UNIX #include #include From 6e8cc8cecd655c29a653f22bf09c203c0205756e Mon Sep 17 00:00:00 2001 From: fgenesis Date: Mon, 22 Jul 2013 13:35:45 +0200 Subject: [PATCH 4/7] Remove FMOD::Channel::setPan(), which is now defunct. --- BBGE/FmodOpenALBridge.cpp | 9 --------- BBGE/FmodOpenALBridge.h | 1 - BBGE/SoundManager.cpp | 4 ---- 3 files changed, 14 deletions(-) diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index 57e5d4b..cc6d09d 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -701,7 +701,6 @@ public: FMOD_RESULT isPlaying(bool *isplaying); FMOD_RESULT setChannelGroup(ChannelGroup *channelgroup); FMOD_RESULT stop(); - FMOD_RESULT setPan(const float pan); FMOD_RESULT setCallback(FMOD_CHANNEL_CALLBACK callback); FMOD_RESULT getUserData(void **userdata); FMOD_RESULT setUserData(void *userdata); @@ -950,14 +949,6 @@ FMOD_RESULT OpenALChannel::setPriority(int _priority) return FMOD_OK; } -ALBRIDGE(Channel,setPan,(float volume),(volume)) -FMOD_RESULT OpenALChannel::setPan(const float pan) -{ - alSource3f(sid, AL_POSITION, pan, 0, 0); - SANITY_CHECK_OPENAL_CALL(); - return FMOD_OK; -} - ALBRIDGE(Channel,stop,(),()) FMOD_RESULT OpenALChannel::stop() { diff --git a/BBGE/FmodOpenALBridge.h b/BBGE/FmodOpenALBridge.h index 0b1f28e..942037d 100644 --- a/BBGE/FmodOpenALBridge.h +++ b/BBGE/FmodOpenALBridge.h @@ -173,7 +173,6 @@ namespace FMOD FMOD_RESULT setPriority(int priority); FMOD_RESULT stop(); FMOD_RESULT setPaused(bool paused); - FMOD_RESULT setPan(float pan); FMOD_RESULT setCallback(FMOD_CHANNEL_CALLBACK callback); FMOD_RESULT getUserData(void **userdata); FMOD_RESULT setUserData(void *userdata); diff --git a/BBGE/SoundManager.cpp b/BBGE/SoundManager.cpp index a3de5a2..0e189e0 100644 --- a/BBGE/SoundManager.cpp +++ b/BBGE/SoundManager.cpp @@ -1055,7 +1055,6 @@ bool SoundManager::playVoice(const std::string &name, SoundVoiceType svt, float checkError(); */ - voiceChannel->setPan(0); voiceChannel->setFrequency(1); voiceChannel->setCallback(NULL); voiceChannel->setUserData(NULL); @@ -1148,8 +1147,6 @@ void *SoundManager::playSfx(const PlaySfx &play) checkError(); } - //channel->setPan(play.pan); - float freq = play.freq; if (freq <= 0) freq = 1; @@ -1341,7 +1338,6 @@ bool SoundManager::playMusic(const std::string &name, SoundLoopType slt, SoundFa } musicChannel->setFrequency(1); // in case the channel was used by a pitch-shifted sound before - musicChannel->setPan(0); musicChannel->setCallback(NULL); musicChannel->setUserData(NULL); musicChannel->set3DMinMaxDistance(0.0f, 0.0f); // disable attenuation From 66802e1935da49c7169e7da0fc341d7d8a9fc2aa Mon Sep 17 00:00:00 2001 From: fgenesis Date: Wed, 24 Jul 2013 04:36:36 +0200 Subject: [PATCH 5/7] 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); From cf6556b94df3f345bb516793cffcd3fe9833107d Mon Sep 17 00:00:00 2001 From: fgenesis Date: Wed, 24 Jul 2013 19:57:13 +0200 Subject: [PATCH 6/7] Implement stereo to mono mixing for proper L/R separation even for stereo samples. This mixes only those sounds that are supposed to be played positional. All centered (default) sounds are played in stereo, as usual. --- Aquaria/DSQ.cpp | 1 + Aquaria/ScriptInterface.cpp | 9 ++++++-- BBGE/FmodOpenALBridge.cpp | 46 ++++++++++++++++++++++++++++++------- BBGE/SoundManager.cpp | 4 ++-- BBGE/SoundManager.h | 5 ++-- 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Aquaria/DSQ.cpp b/Aquaria/DSQ.cpp index 1ea2ab6..ed39c0a 100644 --- a/Aquaria/DSQ.cpp +++ b/Aquaria/DSQ.cpp @@ -2299,6 +2299,7 @@ void DSQ::playPositionalSfx(const std::string &name, const Vector &position, flo sfx.freq = f; sfx.name = name; sfx.relative = false; + sfx.positional = true; sfx.x = position.x; sfx.y = position.y; diff --git a/Aquaria/ScriptInterface.cpp b/Aquaria/ScriptInterface.cpp index 8749efa..f4af088 100644 --- a/Aquaria/ScriptInterface.cpp +++ b/Aquaria/ScriptInterface.cpp @@ -2774,6 +2774,7 @@ luaFunc(entity_playSfx) float fadeOut = lua_tonumber(L, 6); sfx.maxdist = lua_tonumber(L, 7); sfx.relative = false; + sfx.positional = true; h = core->sound->playSfx(sfx); if (fadeOut != 0) @@ -5944,8 +5945,12 @@ luaFunc(playSfx) if (sfx.vol <= 0) sfx.vol = 1; sfx.loops = lua_tointeger(L, 4); - sfx.x = lua_tonumber(L, 5); - sfx.y = lua_tonumber(L, 6); + if(lua_isnumber(L, 6) && lua_isnumber(L, 7)) + { + sfx.x = lua_tonumber(L, 5); + sfx.y = lua_tonumber(L, 6); + sfx.positional = true; + } sfx.maxdist = lua_tonumber(L, 7); if(lua_isnoneornil(L, 8)) sfx.relative = (sfx.x == 0 && sfx.y == 0); diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index 9a7b105..f845248 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -69,8 +69,12 @@ public: ~OggDecoder(); - // Start playing on the given channel, with optional looping. - bool start(ALuint source, bool loop); + // Prepare playing on the given channel. + bool preStart(ALuint source); + + // Decodes the first few buffers, starts the actual playback and detaches the decoder + // from the main thread, with optional looping. + void start(bool loop); // Decode audio into any free buffers. Must be called periodically // on systems without threads; may be called without harm on systems @@ -322,10 +326,9 @@ OggDecoder::~OggDecoder() } } -bool OggDecoder::start(ALuint source, bool loop) +bool OggDecoder::preStart(ALuint source) { this->source = source; - this->loop = loop; if (fp) { if (ov_open_callbacks(fp, &vf, NULL, 0, local_OV_CALLBACKS_NOCLOSE) != 0) @@ -387,6 +390,13 @@ bool OggDecoder::start(ALuint source, bool loop) return false; } + return true; +} + +void OggDecoder::start(bool loop) +{ + this->loop = loop; + playing = true; eof = false; samples_done = 0; @@ -394,8 +404,6 @@ bool OggDecoder::start(ALuint source, bool loop) queue(buffers[i]); detachDecoder(this); - - return true; } void OggDecoder::update() @@ -512,7 +520,22 @@ void OggDecoder::queue(ALuint buffer) if (pcm_size > 0) { - alBufferData(buffer, format, pcm_buffer, pcm_size, freq); + ALuint fmt = format; + if(channels == 2 && forcemono) + { + signed short *buf = (short*)&pcm_buffer[0]; + int numSamples = pcm_size / 2; // 16 bit samples + int j = 0; + for (int i = 0; i < numSamples ; i += 2) + { + // This is in theory not quite correct, but it doesn't add any artifacts or clipping. + // FIXME: Seems that simple and stupid is the method of choice, then... -- FG + buf[j++] = (buf[i] + buf[i+1]) / 2; + } + pcm_size = numSamples; + fmt = AL_FORMAT_MONO16; + } + alBufferData(buffer, fmt, pcm_buffer, pcm_size, freq); alSourceQueueBuffers(source, 1, &buffer); } } @@ -839,7 +862,7 @@ bool OpenALChannel::start(OpenALSound *sound) decoder = new OggDecoder(sound->getFile()); else decoder = new OggDecoder(sound->getData(), sound->getSize()); - if (!decoder->start(sid, sound->isLooping())) + if (!decoder->preStart(sid)) { delete decoder; decoder = NULL; @@ -954,6 +977,11 @@ FMOD_RESULT OpenALChannel::setPaused(const bool _paused, const bool setstate) } else if ((!_paused) && (initial || ((state == AL_INITIAL) || (state == AL_PAUSED)))) { + if (initial) + { + decoder->setForceMono(mindist || maxdist); // HACK: this is set for positional sounds. + decoder->start(sound->isLooping()); + } alSourcePlay(sid); initial = false; SANITY_CHECK_OPENAL_CALL(); @@ -1429,6 +1457,8 @@ FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE alGenBuffers(1, &bid); if (bid != 0) { + // FIXME: This needs to stored seperately and fed to the AL on demand, + // converting stereo to mono when it's known whether to do so or not. alBufferData(bid, format, data, size, freq); *sound = (Sound *) new OpenALSound(bid, (((mode & FMOD_LOOP_OFF) == 0) && (mode & FMOD_LOOP_NORMAL))); ((OpenALSound*)*sound)->setNumChannels(format == AL_FORMAT_STEREO16 ? 2 : 1); diff --git a/BBGE/SoundManager.cpp b/BBGE/SoundManager.cpp index 0e189e0..f57ad1f 100644 --- a/BBGE/SoundManager.cpp +++ b/BBGE/SoundManager.cpp @@ -1158,9 +1158,9 @@ void *SoundManager::playSfx(const PlaySfx &play) // distance gain attenuation: stereo separation + silence at further away than maxdist float maxdist = play.maxdist; if (!maxdist) - maxdist = 1300; + maxdist = 1800; - if(maxdist > 0) + if(maxdist > 0 && play.positional) channel->set3DMinMaxDistance(maxdist * 0.3, maxdist); // HACK: this works reasonably well else channel->set3DMinMaxDistance(0, 0); // no attenuation diff --git a/BBGE/SoundManager.h b/BBGE/SoundManager.h index cd18d65..245388a 100644 --- a/BBGE/SoundManager.h +++ b/BBGE/SoundManager.h @@ -106,7 +106,7 @@ struct PlaySfx { PlaySfx() : priority(0.5), handle(0), vol(1), fade(SFT_NONE), time(0), freq(1), loops(0), channel(BBGE_AUDIO_NOCHANNEL), - maxdist(0), x(0), y(0), relative(true) {} + maxdist(0), x(0), y(0), relative(true), positional(false) {} std::string name; intptr_t handle; @@ -119,7 +119,8 @@ struct PlaySfx float maxdist; // distance gain attenuation. if 0: use default value, -1: don't attenuate at all SoundFadeType fade; float x, y; - bool relative; + bool relative; // relative to listener? + bool positional; // if true, this indicates that we want positional sound (stereo will be downmixed to mono to make OpenAL happy) }; class SoundHolder; // defined below From f825ad9fb701cd516ab0138f19c30fc0874b28cb Mon Sep 17 00:00:00 2001 From: fgenesis Date: Fri, 26 Jul 2013 02:15:40 +0200 Subject: [PATCH 7/7] Fix crash on minimize + reactivate --- BBGE/FmodOpenALBridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index f845248..320f4b4 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -977,7 +977,7 @@ FMOD_RESULT OpenALChannel::setPaused(const bool _paused, const bool setstate) } else if ((!_paused) && (initial || ((state == AL_INITIAL) || (state == AL_PAUSED)))) { - if (initial) + if (initial && decoder) { decoder->setForceMono(mindist || maxdist); // HACK: this is set for positional sounds. decoder->start(sound->isLooping());