Commit 23345b47 authored by David Reid's avatar David Reid

Add support for volume smoothing to sounds.

Smoothing is disabled by default. To enable it, you must use
ma_sound_init_ex() and configure it via the volumeSmoothTimeInPCMFrames
member of ma_sound_config.

This commit also fixes a bug where ma_gainer is not properly applying
smoothing.
parent 9a766349
v0.11.15 - TBD
==============
* Fix a bug in ma_gainer where smoothing isn't applied correctly thus resulting in glitching.
* Add support for smoothing volume changes of sounds with `ma_sound_set_volume()`.
v0.11.14 - 2023-03-29 v0.11.14 - 2023-03-29
===================== =====================
* Fix some pedantic warnings when compiling with GCC. * Fix some pedantic warnings when compiling with GCC.
......
...@@ -11001,6 +11001,7 @@ typedef struct ...@@ -11001,6 +11001,7 @@ typedef struct
ma_uint32 channelsIn; ma_uint32 channelsIn;
ma_uint32 channelsOut; ma_uint32 channelsOut;
ma_uint32 sampleRate; /* Only used when the type is set to ma_engine_node_type_sound. */ ma_uint32 sampleRate; /* Only used when the type is set to ma_engine_node_type_sound. */
ma_uint32 volumeSmoothTimeInPCMFrames; /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */
ma_mono_expansion_mode monoExpansionMode; ma_mono_expansion_mode monoExpansionMode;
ma_bool8 isPitchDisabled; /* Pitching can be explicitly disabled with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ ma_bool8 isPitchDisabled; /* Pitching can be explicitly disabled with MA_SOUND_FLAG_NO_PITCH to optimize processing. */
ma_bool8 isSpatializationDisabled; /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ ma_bool8 isSpatializationDisabled; /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */
...@@ -11016,11 +11017,14 @@ typedef struct ...@@ -11016,11 +11017,14 @@ typedef struct
ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */
ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */
ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */
ma_uint32 volumeSmoothTimeInPCMFrames;
ma_mono_expansion_mode monoExpansionMode; ma_mono_expansion_mode monoExpansionMode;
ma_fader fader; ma_fader fader;
ma_linear_resampler resampler; /* For pitch shift. */ ma_linear_resampler resampler; /* For pitch shift. */
ma_spatializer spatializer; ma_spatializer spatializer;
ma_panner panner; ma_panner panner;
ma_gainer volumeGainer; /* This will only be used if volumeSmoothTimeInPCMFrames is > 0. */
ma_atomic_float volume; /* Defaults to 1. */
MA_ATOMIC(4, float) pitch; MA_ATOMIC(4, float) pitch;
float oldPitch; /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */ float oldPitch; /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */
float oldDopplerPitch; /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */ float oldDopplerPitch; /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */
...@@ -11055,6 +11059,7 @@ typedef struct ...@@ -11055,6 +11059,7 @@ typedef struct
ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */
ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */
ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */ ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */
ma_uint32 volumeSmoothTimeInPCMFrames; /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */
ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */ ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */
ma_uint64 rangeBegInPCMFrames; ma_uint64 rangeBegInPCMFrames;
ma_uint64 rangeEndInPCMFrames; ma_uint64 rangeEndInPCMFrames;
...@@ -48643,7 +48648,7 @@ static /*__attribute__((noinline))*/ ma_result ma_gainer_process_pcm_frames_inte ...@@ -48643,7 +48648,7 @@ static /*__attribute__((noinline))*/ ma_result ma_gainer_process_pcm_frames_inte
/* Initialize the running gain. */ /* Initialize the running gain. */
for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
float t = (pGainer->pOldGains[iChannel] - pGainer->pNewGains[iChannel]) * pGainer->masterVolume; float t = (pGainer->pNewGains[iChannel] - pGainer->pOldGains[iChannel]) * pGainer->masterVolume;
pRunningGainDelta[iChannel] = t * d; pRunningGainDelta[iChannel] = t * d;
pRunningGain[iChannel] = (pGainer->pOldGains[iChannel] * pGainer->masterVolume) + (t * a); pRunningGain[iChannel] = (pGainer->pOldGains[iChannel] * pGainer->masterVolume) + (t * a);
} }
...@@ -73514,8 +73519,16 @@ static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float vo ...@@ -73514,8 +73519,16 @@ static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float vo
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
/* We should always have an active spatializer because it can be enabled and disabled dynamically. We can just use that for hodling our volume. */ ma_atomic_float_set(&pEngineNode->volume, volume);
ma_spatializer_set_master_volume(&pEngineNode->spatializer, volume);
/* If we're not smoothing we should bypass the volume gainer entirely. */
if (pEngineNode->volumeSmoothTimeInPCMFrames == 0) {
/* We should always have an active spatializer because it can be enabled and disabled dynamically. We can just use that for hodling our volume. */
ma_spatializer_set_master_volume(&pEngineNode->spatializer, volume);
} else {
/* We're using volume smoothing, so apply the master volume to the gainer. */
ma_gainer_set_gain(&pEngineNode->volumeGainer, volume);
}
return MA_SUCCESS; return MA_SUCCESS;
} }
...@@ -73532,7 +73545,9 @@ static ma_result ma_engine_node_get_volume(const ma_engine_node* pEngineNode, fl ...@@ -73532,7 +73545,9 @@ static ma_result ma_engine_node_get_volume(const ma_engine_node* pEngineNode, fl
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
return ma_spatializer_get_master_volume(&pEngineNode->spatializer, pVolume); *pVolume = ma_atomic_float_get((ma_atomic_float*)&pEngineNode->volume);
return MA_SUCCESS;
} }
...@@ -73548,6 +73563,7 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo ...@@ -73548,6 +73563,7 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
ma_bool32 isFadingEnabled; ma_bool32 isFadingEnabled;
ma_bool32 isSpatializationEnabled; ma_bool32 isSpatializationEnabled;
ma_bool32 isPanningEnabled; ma_bool32 isPanningEnabled;
ma_bool32 isVolumeSmoothingEnabled;
frameCountIn = *pFrameCountIn; frameCountIn = *pFrameCountIn;
frameCountOut = *pFrameCountOut; frameCountOut = *pFrameCountOut;
...@@ -73558,10 +73574,11 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo ...@@ -73558,10 +73574,11 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
totalFramesProcessedIn = 0; totalFramesProcessedIn = 0;
totalFramesProcessedOut = 0; totalFramesProcessedOut = 0;
isPitchingEnabled = ma_engine_node_is_pitching_enabled(pEngineNode); isPitchingEnabled = ma_engine_node_is_pitching_enabled(pEngineNode);
isFadingEnabled = pEngineNode->fader.volumeBeg != 1 || pEngineNode->fader.volumeEnd != 1; isFadingEnabled = pEngineNode->fader.volumeBeg != 1 || pEngineNode->fader.volumeEnd != 1;
isSpatializationEnabled = ma_engine_node_is_spatialization_enabled(pEngineNode); isSpatializationEnabled = ma_engine_node_is_spatialization_enabled(pEngineNode);
isPanningEnabled = pEngineNode->panner.pan != 0 && channelsOut != 1; isPanningEnabled = pEngineNode->panner.pan != 0 && channelsOut != 1;
isVolumeSmoothingEnabled = pEngineNode->volumeSmoothTimeInPCMFrames > 0;
/* Keep going while we've still got data available for processing. */ /* Keep going while we've still got data available for processing. */
while (totalFramesProcessedOut < frameCountOut) { while (totalFramesProcessedOut < frameCountOut) {
...@@ -73637,6 +73654,19 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo ...@@ -73637,6 +73654,19 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
} }
} }
/*
If we're using smoothing, we won't be applying volume via the spatializer, but instead from a ma_gainer. In this case
we'll want to apply our volume now.
*/
if (isVolumeSmoothingEnabled) {
if (isWorkingBufferValid) {
ma_gainer_process_pcm_frames(&pEngineNode->volumeGainer, pWorkingBuffer, pWorkingBuffer, framesJustProcessedOut);
} else {
ma_gainer_process_pcm_frames(&pEngineNode->volumeGainer, pWorkingBuffer, pRunningFramesIn, framesJustProcessedOut);
isWorkingBufferValid = MA_TRUE;
}
}
/* /*
If at this point we still haven't actually done anything with the working buffer we need If at this point we still haven't actually done anything with the working buffer we need
to just read straight from the input buffer. to just read straight from the input buffer.
...@@ -73668,11 +73698,21 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo ...@@ -73668,11 +73698,21 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
if (channelsIn == channelsOut) { if (channelsIn == channelsOut) {
/* No channel conversion required. Just copy straight to the output buffer. */ /* No channel conversion required. Just copy straight to the output buffer. */
ma_copy_and_apply_volume_factor_f32(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut * channelsOut, volume); if (isVolumeSmoothingEnabled) {
/* Volume has already been applied. Just copy straight to the output buffer. */
ma_copy_pcm_frames(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut * channelsOut, ma_format_f32, channelsOut);
} else {
/* Volume has not been applied yet. Copy and apply volume in the same pass. */
ma_copy_and_apply_volume_factor_f32(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut * channelsOut, volume);
}
} else { } else {
/* Channel conversion required. TODO: Add support for channel maps here. */ /* Channel conversion required. TODO: Add support for channel maps here. */
ma_channel_map_apply_f32(pRunningFramesOut, NULL, channelsOut, pWorkingBuffer, NULL, channelsIn, framesJustProcessedOut, ma_channel_mix_mode_simple, pEngineNode->monoExpansionMode); ma_channel_map_apply_f32(pRunningFramesOut, NULL, channelsOut, pWorkingBuffer, NULL, channelsIn, framesJustProcessedOut, ma_channel_mix_mode_simple, pEngineNode->monoExpansionMode);
ma_apply_volume_factor_f32(pRunningFramesOut, framesJustProcessedOut * channelsOut, volume);
/* If we're using smoothing, the volume will have already been applied. */
if (!isVolumeSmoothingEnabled) {
ma_apply_volume_factor_f32(pRunningFramesOut, framesJustProcessedOut * channelsOut, volume);
}
} }
} }
...@@ -73896,6 +73936,7 @@ typedef struct ...@@ -73896,6 +73936,7 @@ typedef struct
size_t baseNodeOffset; size_t baseNodeOffset;
size_t resamplerOffset; size_t resamplerOffset;
size_t spatializerOffset; size_t spatializerOffset;
size_t gainerOffset;
} ma_engine_node_heap_layout; } ma_engine_node_heap_layout;
static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pConfig, ma_engine_node_heap_layout* pHeapLayout) static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pConfig, ma_engine_node_heap_layout* pHeapLayout)
...@@ -73905,6 +73946,7 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo ...@@ -73905,6 +73946,7 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo
ma_node_config baseNodeConfig; ma_node_config baseNodeConfig;
ma_linear_resampler_config resamplerConfig; ma_linear_resampler_config resamplerConfig;
ma_spatializer_config spatializerConfig; ma_spatializer_config spatializerConfig;
ma_gainer_config gainerConfig;
ma_uint32 channelsIn; ma_uint32 channelsIn;
ma_uint32 channelsOut; ma_uint32 channelsOut;
ma_channel defaultStereoChannelMap[2] = {MA_CHANNEL_SIDE_LEFT, MA_CHANNEL_SIDE_RIGHT}; /* <-- Consistent with the default channel map of a stereo listener. Means channel conversion can run on a fast path. */ ma_channel defaultStereoChannelMap[2] = {MA_CHANNEL_SIDE_LEFT, MA_CHANNEL_SIDE_RIGHT}; /* <-- Consistent with the default channel map of a stereo listener. Means channel conversion can run on a fast path. */
...@@ -73970,6 +74012,20 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo ...@@ -73970,6 +74012,20 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo
pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize);
/* Gainer. Will not be used if we are not using smoothing. */
if (pConfig->volumeSmoothTimeInPCMFrames > 0) {
gainerConfig = ma_gainer_config_init(channelsIn, pConfig->volumeSmoothTimeInPCMFrames);
result = ma_gainer_get_heap_size(&gainerConfig, &tempHeapSize);
if (result != MA_SUCCESS) {
return result;
}
pHeapLayout->gainerOffset = pHeapLayout->sizeInBytes;
pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize);
}
return MA_SUCCESS; return MA_SUCCESS;
} }
...@@ -74003,6 +74059,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p ...@@ -74003,6 +74059,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p
ma_fader_config faderConfig; ma_fader_config faderConfig;
ma_spatializer_config spatializerConfig; ma_spatializer_config spatializerConfig;
ma_panner_config pannerConfig; ma_panner_config pannerConfig;
ma_gainer_config gainerConfig;
ma_uint32 channelsIn; ma_uint32 channelsIn;
ma_uint32 channelsOut; ma_uint32 channelsOut;
ma_channel defaultStereoChannelMap[2] = {MA_CHANNEL_SIDE_LEFT, MA_CHANNEL_SIDE_RIGHT}; /* <-- Consistent with the default channel map of a stereo listener. Means channel conversion can run on a fast path. */ ma_channel defaultStereoChannelMap[2] = {MA_CHANNEL_SIDE_LEFT, MA_CHANNEL_SIDE_RIGHT}; /* <-- Consistent with the default channel map of a stereo listener. Means channel conversion can run on a fast path. */
...@@ -74025,15 +74082,17 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p ...@@ -74025,15 +74082,17 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p
pEngineNode->_pHeap = pHeap; pEngineNode->_pHeap = pHeap;
MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes);
pEngineNode->pEngine = pConfig->pEngine; pEngineNode->pEngine = pConfig->pEngine;
pEngineNode->sampleRate = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pEngineNode->pEngine); pEngineNode->sampleRate = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pEngineNode->pEngine);
pEngineNode->monoExpansionMode = pConfig->monoExpansionMode; pEngineNode->volumeSmoothTimeInPCMFrames = pConfig->volumeSmoothTimeInPCMFrames;
pEngineNode->pitch = 1; pEngineNode->monoExpansionMode = pConfig->monoExpansionMode;
pEngineNode->oldPitch = 1; ma_atomic_float_set(&pEngineNode->volume, 1);
pEngineNode->oldDopplerPitch = 1; pEngineNode->pitch = 1;
pEngineNode->isPitchDisabled = pConfig->isPitchDisabled; pEngineNode->oldPitch = 1;
pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled; pEngineNode->oldDopplerPitch = 1;
pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex; pEngineNode->isPitchDisabled = pConfig->isPitchDisabled;
pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled;
pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex;
channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine);
channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine);
...@@ -74113,6 +74172,18 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p ...@@ -74113,6 +74172,18 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p
goto error3; goto error3;
} }
/* We'll need a gainer for smoothing out volume changes if we have a non-zero smooth time. We apply this before converting to the output channel count. */
if (pConfig->volumeSmoothTimeInPCMFrames > 0) {
gainerConfig = ma_gainer_config_init(channelsIn, pConfig->volumeSmoothTimeInPCMFrames);
result = ma_gainer_init_preallocated(&gainerConfig, ma_offset_ptr(pHeap, heapLayout.gainerOffset), &pEngineNode->volumeGainer);
if (result != MA_SUCCESS) {
goto error3;
}
}
return MA_SUCCESS; return MA_SUCCESS;
/* No need for allocation callbacks here because we use a preallocated heap. */ /* No need for allocation callbacks here because we use a preallocated heap. */
...@@ -74161,6 +74232,10 @@ MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocati ...@@ -74161,6 +74232,10 @@ MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocati
ma_node_uninit(&pEngineNode->baseNode, pAllocationCallbacks); ma_node_uninit(&pEngineNode->baseNode, pAllocationCallbacks);
/* Now that the node has been uninitialized we can safely uninitialize the rest. */ /* Now that the node has been uninitialized we can safely uninitialize the rest. */
if (pEngineNode->volumeSmoothTimeInPCMFrames > 0) {
ma_gainer_uninit(&pEngineNode->volumeGainer, pAllocationCallbacks);
}
ma_spatializer_uninit(&pEngineNode->spatializer, pAllocationCallbacks); ma_spatializer_uninit(&pEngineNode->spatializer, pAllocationCallbacks);
ma_linear_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks); ma_linear_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks);
...@@ -75099,9 +75174,10 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con ...@@ -75099,9 +75174,10 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con
source that provides this information upfront. source that provides this information upfront.
*/ */
engineNodeConfig = ma_engine_node_config_init(pEngine, type, pConfig->flags); engineNodeConfig = ma_engine_node_config_init(pEngine, type, pConfig->flags);
engineNodeConfig.channelsIn = pConfig->channelsIn; engineNodeConfig.channelsIn = pConfig->channelsIn;
engineNodeConfig.channelsOut = pConfig->channelsOut; engineNodeConfig.channelsOut = pConfig->channelsOut;
engineNodeConfig.monoExpansionMode = pConfig->monoExpansionMode; engineNodeConfig.volumeSmoothTimeInPCMFrames = pConfig->volumeSmoothTimeInPCMFrames;
engineNodeConfig.monoExpansionMode = pConfig->monoExpansionMode;
/* If we're loading from a data source the input channel count needs to be the data source's native channel count. */ /* If we're loading from a data source the input channel count needs to be the data source's native channel count. */
if (pConfig->pDataSource != NULL) { if (pConfig->pDataSource != NULL) {
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment