Rework of the attenuation model system.

Support for custom attenuation models has been added.

Attenuation models are now a callback rather than an enumerator. The
callback takes a user data pointer in case the standard distance/rolloff
variables do not suffice. The following APIs have changed in order to
accommodate this:

    ma_spatializer_set_attenuation_model()
    ma_sound_set_attenuation_model()
    ma_sound_group_set_attenuation_model()

These now take an extra parameter for the user data pointer. For stock
attenuation models this should always be set to NULL:

    ma_sound_set_attenuation_model(&sound,
        ma_attenuation_model_linear, NULL);

To plug in a custom model, just implement a callback and plug it in:

    float your_custom_attenuation_model(
        void* pUserData,
        float distance,
        float minDistance,
        float maxDistance,
        float rolloff)
    {
        // Return a value between 0 (silence) and 1 (full volume).
    }

    ...

    ma_sound_set_attenuation_model(&sound,
        your_custom_attenuation_model, &userDataIfRequiredByYourModel);

Previously, setting the attenuation model to none would disable
spatialization completely. This behaviour has changed and now
spatialization will still be applied in this case. You should use
`ma_sound_set_spatialization_enabled(&sound, MA_FALSE)` instead if you
want to disable spatialization.
This commit is contained in:
David Reid
2026-07-13 06:36:23 +10:00
parent 7cb28fcbc0
commit d29e673881
+81 -42
View File
@@ -1616,10 +1616,10 @@ default the attenuation model is set to `ma_attenuation_model_inverse` which is
OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so: OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so:
```c ```c
ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse); ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse, NULL);
``` ```
The supported attenuation models include the following: The supported stock attenuation models include the following:
+----------------------------------+----------------------------------------------+ +----------------------------------+----------------------------------------------+
| ma_attenuation_model_none | No distance attenuation. | | ma_attenuation_model_none | No distance attenuation. |
@@ -1631,6 +1631,26 @@ The supported attenuation models include the following:
| ma_attenuation_model_exponential | Exponential attenuation. | | ma_attenuation_model_exponential | Exponential attenuation. |
+----------------------------------+----------------------------------------------+ +----------------------------------+----------------------------------------------+
The second parameter is a user data pointer which for stock attenuation models should always be set
to null. A custom attenuation model can be plugged in like so:
```c
float your_custom_attenuation_model(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff)
{
// This is just linear attenuation. You would plug in your own model here.
if (minDistance >= maxDistance) {
return 1; // To avoid division by zero. Do not attenuate.
}
return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance);
}
ma_sound_set_attenuation_model(&sound, your_custom_attenuation_model, &userDataIfRequiredByYourModel);
```
The attenuation model cannot be changed while the sound is processing. You should set the
attenuation model once at initialization time before the sound is started.
To control how quickly a sound rolls off as it moves away from the listener, you need to configure To control how quickly a sound rolls off as it moves away from the listener, you need to configure
the rolloff: the rolloff:
@@ -5753,13 +5773,22 @@ typedef struct
ma_spinlock lock; ma_spinlock lock;
} ma_atomic_vec3f; } ma_atomic_vec3f;
typedef enum
{ /* Callback for attenuation models. The user data is unused for all stock models. */
ma_attenuation_model_none, /* No distance attenuation and no spatialization. */ typedef float (* ma_attenuation_model)(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff);
ma_attenuation_model_inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */
ma_attenuation_model_linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ /* No distance attenuation. */
ma_attenuation_model_exponential /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ MA_API float ma_attenuation_model_none(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff);
} ma_attenuation_model;
/* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */
MA_API float ma_attenuation_model_inverse(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff);
/* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */
MA_API float ma_attenuation_model_linear(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff);
/* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */
MA_API float ma_attenuation_model_exponential(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff);
typedef enum typedef enum
{ {
@@ -5829,6 +5858,7 @@ typedef struct
ma_uint32 channelsOut; ma_uint32 channelsOut;
ma_channel* pChannelMapIn; ma_channel* pChannelMapIn;
ma_attenuation_model attenuationModel; ma_attenuation_model attenuationModel;
void* pAttenuationUserData;
ma_positioning positioning; ma_positioning positioning;
ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
float minGain; float minGain;
@@ -5854,6 +5884,7 @@ typedef struct
ma_uint32 channelsOut; ma_uint32 channelsOut;
ma_channel* pChannelMapIn; ma_channel* pChannelMapIn;
ma_attenuation_model attenuationModel; ma_attenuation_model attenuationModel;
void* pAttenuationUserData;
ma_positioning positioning; ma_positioning positioning;
ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */
float minGain; float minGain;
@@ -5889,7 +5920,7 @@ MA_API ma_result ma_spatializer_set_master_volume(ma_spatializer* pSpatializer,
MA_API ma_result ma_spatializer_get_master_volume(const ma_spatializer* pSpatializer, float* pVolume); MA_API ma_result ma_spatializer_get_master_volume(const ma_spatializer* pSpatializer, float* pVolume);
MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer); MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer);
MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer); MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer);
MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel); MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel, void* pAttenuationUserData);
MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer); MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer);
MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning); MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning);
MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer); MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer);
@@ -12180,7 +12211,7 @@ MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z);
MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound); MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound);
MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z); MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z);
MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound); MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound);
MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel); MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel, void* pAttenuationUserData);
MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound); MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound);
MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning); MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning);
MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound); MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound);
@@ -12251,7 +12282,7 @@ MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float
MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup); MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup);
MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z); MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z);
MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup); MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup);
MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel); MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel, void* pAttenuationUserData);
MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup); MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup);
MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning); MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning);
MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup); MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup);
@@ -60034,9 +60065,20 @@ static ma_vec3f ma_get_channel_direction(ma_channel channel)
} }
MA_API float ma_attenuation_model_none(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff)
static float ma_attenuation_inverse(float distance, float minDistance, float maxDistance, float rolloff)
{ {
(void)pUserData;
(void)distance;
(void)minDistance;
(void)maxDistance;
(void)rolloff;
return 1;
}
MA_API float ma_attenuation_model_inverse(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff)
{
(void)pUserData;
if (minDistance >= maxDistance) { if (minDistance >= maxDistance) {
return 1; /* To avoid division by zero. Do not attenuate. */ return 1; /* To avoid division by zero. Do not attenuate. */
} }
@@ -60044,8 +60086,10 @@ static float ma_attenuation_inverse(float distance, float minDistance, float max
return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance)); return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance));
} }
static float ma_attenuation_linear(float distance, float minDistance, float maxDistance, float rolloff) MA_API float ma_attenuation_model_linear(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff)
{ {
(void)pUserData;
if (minDistance >= maxDistance) { if (minDistance >= maxDistance) {
return 1; /* To avoid division by zero. Do not attenuate. */ return 1; /* To avoid division by zero. Do not attenuate. */
} }
@@ -60053,8 +60097,10 @@ static float ma_attenuation_linear(float distance, float minDistance, float maxD
return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance); return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance);
} }
static float ma_attenuation_exponential(float distance, float minDistance, float maxDistance, float rolloff) MA_API float ma_attenuation_model_exponential(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff)
{ {
(void)pUserData;
if (minDistance >= maxDistance) { if (minDistance >= maxDistance) {
return 1; /* To avoid division by zero. Do not attenuate. */ return 1; /* To avoid division by zero. Do not attenuate. */
} }
@@ -60063,6 +60109,8 @@ static float ma_attenuation_exponential(float distance, float minDistance, float
} }
/* /*
Doppler Effect calculation taken from the OpenAL spec, with two main differences: Doppler Effect calculation taken from the OpenAL spec, with two main differences:
@@ -60573,6 +60621,7 @@ MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* p
pSpatializer->channelsIn = pConfig->channelsIn; pSpatializer->channelsIn = pConfig->channelsIn;
pSpatializer->channelsOut = pConfig->channelsOut; pSpatializer->channelsOut = pConfig->channelsOut;
pSpatializer->attenuationModel = pConfig->attenuationModel; pSpatializer->attenuationModel = pConfig->attenuationModel;
pSpatializer->pAttenuationUserData = pConfig->pAttenuationUserData;
pSpatializer->positioning = pConfig->positioning; pSpatializer->positioning = pConfig->positioning;
pSpatializer->handedness = pConfig->handedness; pSpatializer->handedness = pConfig->handedness;
pSpatializer->minGain = pConfig->minGain; pSpatializer->minGain = pConfig->minGain;
@@ -60718,6 +60767,7 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer,
pChannelMapIn = pSpatializer->pChannelMapIn; pChannelMapIn = pSpatializer->pChannelMapIn;
pChannelMapOut = pListener->config.pChannelMapOut; pChannelMapOut = pListener->config.pChannelMapOut;
#if 0
/* If we're not spatializing we need to run an optimized path. */ /* If we're not spatializing we need to run an optimized path. */
if (ma_atomic_load_i32(&pSpatializer->attenuationModel) == ma_attenuation_model_none) { if (ma_atomic_load_i32(&pSpatializer->attenuationModel) == ma_attenuation_model_none) {
if (ma_spatializer_listener_is_enabled(pListener)) { if (ma_spatializer_listener_is_enabled(pListener)) {
@@ -60737,7 +60787,9 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer,
the correct thinking so might need to review this later. the correct thinking so might need to review this later.
*/ */
pSpatializer->dopplerPitch = 1; pSpatializer->dopplerPitch = 1;
} else { } else
#endif
{
/* /*
Let's first determine which listener the sound is closest to. Need to keep in mind that we Let's first determine which listener the sound is closest to. Need to keep in mind that we
might not have a world or any listeners, in which case we just spatializer based on the might not have a world or any listeners, in which case we just spatializer based on the
@@ -60780,24 +60832,10 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer,
distance = ma_vec3f_len(relativePos); distance = ma_vec3f_len(relativePos);
/* We've gathered the data, so now we can apply some spatialization. */ /* We've gathered the data, so now we can apply some spatialization. */
switch (ma_spatializer_get_attenuation_model(pSpatializer)) { if (pSpatializer->attenuationModel != NULL) {
case ma_attenuation_model_inverse: gain = pSpatializer->attenuationModel(pSpatializer->pAttenuationUserData, distance, minDistance, maxDistance, rolloff);
{ } else {
gain = ma_attenuation_inverse(distance, minDistance, maxDistance, rolloff); gain = ma_attenuation_model_none(NULL, distance, minDistance, maxDistance, rolloff);
} break;
case ma_attenuation_model_linear:
{
gain = ma_attenuation_linear(distance, minDistance, maxDistance, rolloff);
} break;
case ma_attenuation_model_exponential:
{
gain = ma_attenuation_exponential(distance, minDistance, maxDistance, rolloff);
} break;
case ma_attenuation_model_none:
default:
{
gain = 1;
} break;
} }
/* Normalize the position. */ /* Normalize the position. */
@@ -61048,13 +61086,14 @@ MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpati
return pSpatializer->channelsOut; return pSpatializer->channelsOut;
} }
MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel) MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel, void* pAttenuationUserData)
{ {
if (pSpatializer == NULL) { if (pSpatializer == NULL) {
return; return;
} }
ma_atomic_exchange_i32(&pSpatializer->attenuationModel, attenuationModel); pSpatializer->attenuationModel = attenuationModel;
pSpatializer->pAttenuationUserData = pAttenuationUserData;
} }
MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer) MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer)
@@ -61063,7 +61102,7 @@ MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatia
return ma_attenuation_model_none; return ma_attenuation_model_none;
} }
return (ma_attenuation_model)ma_atomic_load_i32(&pSpatializer->attenuationModel); return pSpatializer->attenuationModel;
} }
MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning) MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning)
@@ -88507,13 +88546,13 @@ MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound)
return ma_spatializer_get_velocity(&pSound->engineNode.spatializer); return ma_spatializer_get_velocity(&pSound->engineNode.spatializer);
} }
MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel) MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel, void* pAttenuationUserData)
{ {
if (pSound == NULL) { if (pSound == NULL) {
return; return;
} }
ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel); ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel, pAttenuationUserData);
} }
MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound) MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound)
@@ -89213,9 +89252,9 @@ MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup)
return ma_sound_get_velocity(pGroup); return ma_sound_get_velocity(pGroup);
} }
MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel) MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel, void* pAttenuationUserData)
{ {
ma_sound_set_attenuation_model(pGroup, attenuationModel); ma_sound_set_attenuation_model(pGroup, attenuationModel, pAttenuationUserData);
} }
MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup) MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup)