#ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H #include #include "sound_output.hpp" namespace MWSound { // Extra play flags, not intended for caller use enum PlayModeEx { Play_2D = 0, Play_StopAtFadeEnd = 1 << 28, Play_FadeExponential = 1 << 29, Play_InFade = 1 << 30, Play_3D = 1 << 31, Play_FadeFlagsMask = (Play_StopAtFadeEnd | Play_FadeExponential), }; // For testing individual PlayMode flags inline int operator&(int a, PlayMode b) { return a & static_cast(b); } inline int operator&(PlayMode a, PlayMode b) { return static_cast(a) & static_cast(b); } struct SoundParams { osg::Vec3f mPos; float mVolume = 1.0f; float mBaseVolume = 1.0f; float mPitch = 1.0f; float mMinDistance = 1.0f; float mMaxDistance = 1000.0f; int mFlags = 0; float mFadeVolume = 1.0f; float mFadeTarget = 0.0f; float mFadeStep = 0.0f; }; class SoundBase { SoundBase& operator=(const SoundBase&) = delete; SoundBase(const SoundBase&) = delete; SoundBase(SoundBase&&) = delete; SoundParams mParams; protected: Sound_Instance mHandle = nullptr; friend class OpenAL_Output; public: void setPosition(const osg::Vec3f& pos) { mParams.mPos = pos; } void setVolume(float volume) { mParams.mVolume = volume; } void setBaseVolume(float volume) { mParams.mBaseVolume = volume; } void setFadeout(float duration) { setFade(duration, 0.0, Play_StopAtFadeEnd); } /// Fade to the given linear gain within the specified amount of time. /// Note that the fade gain is independent of the sound volume. /// /// \param duration specifies the duration of the fade. For *linear* /// fades (default) this will be exactly the time at which the desired /// volume is reached. Let v0 be the initial volume, v1 be the target /// volume, and t0 be the initial time. Then the volume over time is /// given as /// /// v(t) = v0 + (v1 - v0) * (t - t0) / duration if t <= t0 + duration /// v(t) = v1 if t > t0 + duration /// /// For *exponential* fades this determines the time-constant of the /// exponential process describing the fade. In particular, we guarantee /// that we reach v0 + 0.99 * (v1 - v0) within the given duration. /// /// v(t) = v1 + (v0 - v1) * exp(-4.6 * (t0 - t) / duration) /// /// where -4.6 is approximately log(1%) (i.e., -40 dB). /// /// This interpolation mode is meant for environmental sound effects to /// achieve less jarring transitions. /// /// \param targetVolume is the linear gain that should be reached at /// the end of the fade. /// /// \param flags may be a combination of Play_FadeExponential and /// Play_StopAtFadeEnd. If Play_StopAtFadeEnd is set, stops the sound /// once the fade duration has passed or the target volume has been /// reached. If Play_FadeExponential is set, enables the exponential /// fade mode (see above). void setFade(float duration, float targetVolume, int flags = 0) { // Approximation of log(1%) (i.e., -40 dB). constexpr float minus40Decibel = -4.6f; // Do nothing if already at the target, unless we need to trigger a stop event if ((mParams.mFadeVolume == targetVolume) && !(flags & Play_StopAtFadeEnd)) return; mParams.mFadeTarget = targetVolume; mParams.mFlags = (mParams.mFlags & ~Play_FadeFlagsMask) | (flags & Play_FadeFlagsMask) | Play_InFade; if (duration > 0.0f) { if (mParams.mFlags & Play_FadeExponential) mParams.mFadeStep = -minus40Decibel / duration; else mParams.mFadeStep = (mParams.mFadeTarget - mParams.mFadeVolume) / duration; } else { mParams.mFadeVolume = mParams.mFadeTarget; mParams.mFadeStep = 0.0f; } } /// Updates the internal fading logic. /// /// \param dt is the time in seconds since the last call to update. /// /// \return true if the sound is still active, false if the sound has /// reached a fading destination that was marked with Play_StopAtFadeEnd. bool updateFade(float dt) { // Mark fade as done at this volume difference (-80dB when fading to zero) constexpr float minVolumeDifference = 1e-4f; if (!getInFade()) return true; // Perform the actual fade operation const float deltaBefore = mParams.mFadeTarget - mParams.mFadeVolume; if (mParams.mFlags & Play_FadeExponential) mParams.mFadeVolume += mParams.mFadeStep * deltaBefore * dt; else mParams.mFadeVolume += mParams.mFadeStep * dt; const float deltaAfter = mParams.mFadeTarget - mParams.mFadeVolume; // Abort fade if we overshot or reached the minimum difference if ((std::signbit(deltaBefore) != std::signbit(deltaAfter)) || (std::abs(deltaAfter) < minVolumeDifference)) { mParams.mFadeVolume = mParams.mFadeTarget; mParams.mFlags &= ~Play_InFade; } return getInFade() || !(mParams.mFlags & Play_StopAtFadeEnd); } const osg::Vec3f& getPosition() const { return mParams.mPos; } float getRealVolume() const { return mParams.mVolume * mParams.mBaseVolume * mParams.mFadeVolume; } float getPitch() const { return mParams.mPitch; } float getMinDistance() const { return mParams.mMinDistance; } float getMaxDistance() const { return mParams.mMaxDistance; } MWSound::Type getPlayType() const { return static_cast(mParams.mFlags & MWSound::Type::Mask); } bool getUseEnv() const { return !(mParams.mFlags & MWSound::PlayMode::NoEnv); } bool getIsLooping() const { return mParams.mFlags & MWSound::PlayMode::Loop; } bool getDistanceCull() const { return mParams.mFlags & MWSound::PlayMode::RemoveAtDistance; } bool getIs3D() const { return mParams.mFlags & Play_3D; } bool getInFade() const { return mParams.mFlags & Play_InFade; } void init(const SoundParams& params) { mParams = params; mHandle = nullptr; } SoundBase() = default; }; class Sound : public SoundBase { Sound& operator=(const Sound&) = delete; Sound(const Sound&) = delete; Sound(Sound&&) = delete; public: Sound() = default; }; class Stream : public SoundBase { Stream& operator=(const Stream&) = delete; Stream(const Stream&) = delete; Stream(Stream&&) = delete; public: Stream() = default; }; } #endif