-------------------------------------------------------------------------------- -- | -- Module : Sound.OpenAL.AL.Attenuation -- Copyright : (c) Sven Panne 2005 -- License : BSD-style (see the file libraries/OpenAL/LICENSE) -- -- Maintainer : sven.panne@aedion.de -- Stability : provisional -- Portability : portable -- -- This module corresponds to section 3.4 (Attenuation By Distance) of the -- OpenAL Specification and Reference (version 1.1). -- -------------------------------------------------------------------------------- module Sound.OpenAL.AL.Attenuation ( -- * Introduction -- $Introduction -- * Handling the Distance Model DistanceModel(..), distanceModel -- * Evaluation of Gain\/Attenuation Related State -- $EvaluationOfGainAttenuationRelatedState -- * No Culling By Distance -- $NoCullingByDistance ) where import Foreign.Marshal.Alloc ( alloca ) import Foreign.Ptr ( Ptr ) import Graphics.Rendering.OpenGL.GL.StateVar ( StateVar, makeStateVar ) import Sound.OpenAL.AL.BasicTypes ( ALint, ALenum ) import Sound.OpenAL.AL.PeekPoke ( peek1 ) import Sound.OpenAL.AL.QueryUtils ( GetPName(GetDistanceModel), marshalGetPName ) import Sound.OpenAL.Constants ( al_NONE, al_INVERSE_DISTANCE, al_INVERSE_DISTANCE_CLAMPED, al_LINEAR_DISTANCE, al_LINEAR_DISTANCE_CLAMPED, al_EXPONENT_DISTANCE, al_EXPONENT_DISTANCE_CLAMPED ) #ifdef __HADDOCK__ import Sound.OpenAL.AL.Source ( sourceGain, gainBounds, coneAngles, coneOuterGain, referenceDistance, rolloffFactor, maxDistance ) #endif -------------------------------------------------------------------------------- -- $Introduction -- Samples usually use the entire dynamic range of the chosen format\/encoding, -- independent of their real world intensity. In other words, a jet engine and a -- clockwork both will have samples with full amplitude. The application will -- then have to adjust source gain accordingly to account for relative -- differences. -- -- Source gain is then attenuated by distance. The effective attenuation of a -- source depends on many factors, among which distance attenuation and source -- and listener gain are only some of the contributing factors. Even if the -- source and listener gain exceed 1 (amplification beyond the guaranteed -- dynamic range), distance and other attenuation might ultimately limit the -- overall gain to a value below 1. -------------------------------------------------------------------------------- -- $EvaluationOfGainAttenuationRelatedState -- While amplification\/attenuation commute (multiplication of scaling factors), -- clamping operations do not. The order in which various gain related -- operations are applied is: -- -- 1. Distance attenuation is calculated first, including minimum -- ('referenceDistance') and maximum ('maxDistance') thresholds. -- -- 2. The result is then multiplied by source gain. -- -- 3. If the source is directional (the inner cone angle is less than the outer -- cone angle, see 'coneAngles'), an angle-dependent attenuation is calculated -- depending on 'coneOuterGain', and multiplied with the distance-dependent -- attenuation. The resulting attenuation factor for the given angle and -- distance between listener and source is multiplied with 'sourceGain'. -- -- 4. The effective gain computed this way is compared against 'gainBounds'. -- -- 5. The result is guaranteed to be clamped to 'gainBounds', and subsequently -- multiplied by listener gain which serves as an overall volume control. -- -- The implementation is free to clamp listener gain if necessary due to -- hardware or implementation constraints. -------------------------------------------------------------------------------- -- $NoCullingByDistance -- With the DS3D compatible inverse clamped distance model, OpenAL provides a -- per-source 'maxDistance' attribute that can be used to define a distance -- beyond which the source will not be further attenuated by distance. The DS3D -- distance attenuation model and its clamping of volume is also extended by a -- mechanism to cull (mute) sources from processing, based on distance. However, -- the OpenAL does not support culling a source from processing based on a -- distance threshold. -- -- At this time OpenAL is not meant to support culling at all. Culling based on -- distance, or bounding volumes, or other criteria, is best left to the -- application. For example, the application might employ sophisticated -- techniques to determine whether sources are audible that are beyond the scope -- of OpenAL. In particular, rule based culling inevitably introduces acoustic -- artifacts. E.g. if the listener-source distance is nearly equal to the culling -- threshold distance, but varies above and below, there will be popping -- artifacts in the absence of hysteresis. -------------------------------------------------------------------------------- -- | OpenAL currently supports six modes of operation with respect to distance -- attenuation, including one that is similar to the IASIG I3DL2 model. The -- application chooses one of these models (or chooses to disable -- distance-dependent attenuation) on a per-context basis. -- -- The distance used in the formulas for the \"clamped\" modes below is clamped -- to be in the range between 'referenceDistance' and 'maxDistance': -- -- /clamped distance/ = -- max('referenceDistance', min(/distance/, 'maxDistance')) -- -- The linear models are not physically realistic, but do allow full attenuation -- of a source beyond a specified distance. The OpenAL implementation is still -- free to apply any range clamping as necessary. -- -- With all the distance models, if the formula can not be evaluated then the -- source will not be attenuated. For example, if a linear model is being used -- with 'referenceDistance' equal to 'maxDistance', then the gain equation will -- have a divide-by-zero error in it. In this case, there is no attenuation for -- that source. data DistanceModel = NoAttenuation -- ^ Bypass all distance attenuation calculation for all sources. The -- implementation is expected to optimize this situation. | InverseDistance -- ^ Inverse distance rolloff model, which is equivalent to the IASIG I3DL2 -- model with the exception that 'referenceDistance' does not imply any -- clamping. -- -- /gain/ = 'referenceDistance' \/ ('referenceDistance' + -- 'rolloffFactor' \* (/distance/ - 'referenceDistance')) -- -- The 'referenceDistance' parameter used here is a per-source attribute -- which is the distance at which the listener will experience gain -- (unless the implementation had to clamp effective gain to the available -- dynamic range). 'rolloffFactor' is per-source parameter the application -- can use to increase or decrease the range of a source by decreasing or -- increasing the attenuation, respectively. The default value is 1. The -- implementation is free to optimize for a 'rolloffFactor' value of 0, -- which indicates that the application does not wish any distance -- attenuation on the respective source. | InverseDistanceClamped -- ^ Inverse Distance clamped model, which is essentially the inverse -- distance rolloff model, extended to guarantee that for distances below -- 'referenceDistance', gain is clamped. This mode is equivalent to the -- IASIG I3DL2 distance model. | LinearDistance -- ^ Linear distance rolloff model, modeling a linear dropoff in gain as -- distance increases between the source and listener. -- -- /gain/ = (1 - 'rolloffFactor' \* (/distance/ - 'referenceDistance') \/ -- ('maxDistance' - 'referenceDistance')) | LinearDistanceClamped -- ^ Linear Distance clamped model, which is the linear model, extended to -- guarantee that for distances below 'referenceDistance', gain is clamped. | ExponentDistance -- ^ Exponential distance rolloff model, modeling an exponential dropoff in -- gain as distance increases between the source and listener. -- -- /gain/ = (/distance/ \/ 'referenceDistance') \*\* (- 'rolloffFactor') | ExponentDistanceClamped -- ^ Exponential Distance clamped model, which is the exponential model, -- extended to guarantee that for distances below 'referenceDistance', -- gain is clamped. deriving ( Eq, Ord, Show ) marshalDistanceModel :: DistanceModel -> ALenum marshalDistanceModel x = case x of NoAttenuation -> al_NONE InverseDistance -> al_INVERSE_DISTANCE InverseDistanceClamped -> al_INVERSE_DISTANCE_CLAMPED LinearDistance -> al_LINEAR_DISTANCE LinearDistanceClamped -> al_LINEAR_DISTANCE_CLAMPED ExponentDistance -> al_EXPONENT_DISTANCE ExponentDistanceClamped -> al_EXPONENT_DISTANCE_CLAMPED unmarshalDistanceModel :: ALenum -> DistanceModel unmarshalDistanceModel x | x == al_NONE = NoAttenuation | x == al_INVERSE_DISTANCE = InverseDistance | x == al_INVERSE_DISTANCE_CLAMPED = InverseDistanceClamped | x == al_LINEAR_DISTANCE = LinearDistance | x == al_LINEAR_DISTANCE_CLAMPED = LinearDistanceClamped | x == al_EXPONENT_DISTANCE = ExponentDistance | x == al_EXPONENT_DISTANCE_CLAMPED = ExponentDistanceClamped | otherwise = error ("unmarshalDistanceModel: illegal value " ++ show x) -- | Contains the current per-context distance model. distanceModel :: StateVar DistanceModel distanceModel = makeStateVar (alloca $ \buf -> do alGetIntegerv (marshalGetPName GetDistanceModel) buf peek1 (unmarshalDistanceModel . fromIntegral) buf) (alDistanceModel . marshalDistanceModel) foreign import CALLCONV unsafe "alGetIntegerv" alGetIntegerv :: ALenum -> Ptr ALint -> IO () foreign import CALLCONV unsafe "alDistanceModel" alDistanceModel :: ALenum -> IO ()