mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-24 01:04:02 +02:00
Add initial implementation for scheduled fades.
This adds the following APIs: * ma_sound_set_fade_start_in_pcm_frames() * ma_sound_set_fade_start_in_milliseconds() Public issue https://github.com/mackron/miniaudio/issues/669
This commit is contained in:
+74
-16
@@ -11069,6 +11069,15 @@ typedef struct
|
|||||||
MA_ATOMIC(4, ma_bool32) isSpatializationDisabled; /* Set to false by default. When set to false, will not have spatialisation applied. */
|
MA_ATOMIC(4, ma_bool32) isSpatializationDisabled; /* Set to false by default. When set to false, will not have spatialisation applied. */
|
||||||
MA_ATOMIC(4, ma_uint32) pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
|
MA_ATOMIC(4, ma_uint32) pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */
|
||||||
|
|
||||||
|
/* When setting a fade, it's not done immediately in ma_sound_set_fade(). It's deferred to the audio thread which means we need to store the settings here. */
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
ma_atomic_float volumeBeg;
|
||||||
|
ma_atomic_float volumeEnd;
|
||||||
|
ma_atomic_uint64 fadeLengthInFrames; /* <-- Defaults to (~(ma_uint64)0) which is used to indicate that no fade should be applied. */
|
||||||
|
ma_atomic_uint64 absoluteGlobalTimeInFrames; /* <-- The time to start the fade. */
|
||||||
|
} fadeSettings;
|
||||||
|
|
||||||
/* Memory management. */
|
/* Memory management. */
|
||||||
ma_bool8 _ownsHeap;
|
ma_bool8 _ownsHeap;
|
||||||
void* _pHeap;
|
void* _pHeap;
|
||||||
@@ -11310,6 +11319,8 @@ MA_API void ma_sound_set_directional_attenuation_factor(ma_sound* pSound, float
|
|||||||
MA_API float ma_sound_get_directional_attenuation_factor(const ma_sound* pSound);
|
MA_API float ma_sound_get_directional_attenuation_factor(const ma_sound* pSound);
|
||||||
MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames);
|
MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames);
|
||||||
MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds);
|
MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds);
|
||||||
|
MA_API void ma_sound_set_fade_start_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames, ma_uint64 absoluteGlobalTimeInFrames);
|
||||||
|
MA_API void ma_sound_set_fade_start_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds, ma_uint64 absoluteGlobalTimeInMilliseconds);
|
||||||
MA_API float ma_sound_get_current_fade_volume(const ma_sound* pSound);
|
MA_API float ma_sound_get_current_fade_volume(const ma_sound* pSound);
|
||||||
MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames);
|
MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames);
|
||||||
MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds);
|
MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds);
|
||||||
@@ -49345,14 +49356,6 @@ MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut,
|
|||||||
return MA_INVALID_ARGS;
|
return MA_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
For now we need to clamp frameCount so that the cursor never overflows 32-bits. This is required for
|
|
||||||
the conversion to a float which we use for the linear interpolation. This might be changed later.
|
|
||||||
*/
|
|
||||||
if (frameCount + pFader->cursorInFrames > UINT_MAX) {
|
|
||||||
frameCount = UINT_MAX - pFader->cursorInFrames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the cursor is still negative we need to just copy the absolute number of those frames, but no more than frameCount. */
|
/* If the cursor is still negative we need to just copy the absolute number of those frames, but no more than frameCount. */
|
||||||
if (pFader->cursorInFrames < 0) {
|
if (pFader->cursorInFrames < 0) {
|
||||||
ma_uint64 absCursorInFrames = (ma_uint64)0 - pFader->cursorInFrames;
|
ma_uint64 absCursorInFrames = (ma_uint64)0 - pFader->cursorInFrames;
|
||||||
@@ -49368,6 +49371,15 @@ MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut,
|
|||||||
pFramesIn = ma_offset_ptr(pFramesIn, ma_get_bytes_per_frame(pFader->config.format, pFader->config.channels)*absCursorInFrames);
|
pFramesIn = ma_offset_ptr(pFramesIn, ma_get_bytes_per_frame(pFader->config.format, pFader->config.channels)*absCursorInFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pFader->cursorInFrames >= 0) {
|
||||||
|
/*
|
||||||
|
For now we need to clamp frameCount so that the cursor never overflows 32-bits. This is required for
|
||||||
|
the conversion to a float which we use for the linear interpolation. This might be changed later.
|
||||||
|
*/
|
||||||
|
if (frameCount + pFader->cursorInFrames > UINT_MAX) {
|
||||||
|
frameCount = UINT_MAX - pFader->cursorInFrames;
|
||||||
|
}
|
||||||
|
|
||||||
/* Optimized path if volumeBeg and volumeEnd are equal. */
|
/* Optimized path if volumeBeg and volumeEnd are equal. */
|
||||||
if (pFader->volumeBeg == pFader->volumeEnd) {
|
if (pFader->volumeBeg == pFader->volumeEnd) {
|
||||||
if (pFader->volumeBeg == 1) {
|
if (pFader->volumeBeg == 1) {
|
||||||
@@ -49379,7 +49391,7 @@ MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Slower path. Volumes are different, so may need to do an interpolation. */
|
/* Slower path. Volumes are different, so may need to do an interpolation. */
|
||||||
if (pFader->cursorInFrames >= pFader->lengthInFrames) {
|
if ((ma_uint64)pFader->cursorInFrames >= pFader->lengthInFrames) {
|
||||||
/* Fast path. We've gone past the end of the fade period so just apply the end volume to all samples. */
|
/* Fast path. We've gone past the end of the fade period so just apply the end volume to all samples. */
|
||||||
ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd);
|
ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd);
|
||||||
} else {
|
} else {
|
||||||
@@ -49405,6 +49417,7 @@ MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pFader->cursorInFrames += frameCount;
|
pFader->cursorInFrames += frameCount;
|
||||||
|
|
||||||
@@ -49432,7 +49445,7 @@ MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat,
|
|||||||
|
|
||||||
MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames)
|
MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames)
|
||||||
{
|
{
|
||||||
ma_fader_set_fade(pFader, volumeBeg, volumeEnd, lengthInFrames, 0);
|
ma_fader_set_fade_ex(pFader, volumeBeg, volumeEnd, lengthInFrames, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
MA_API void ma_fader_set_fade_ex(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames, ma_uint64 startOffsetInFrames)
|
MA_API void ma_fader_set_fade_ex(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames, ma_uint64 startOffsetInFrames)
|
||||||
@@ -49479,7 +49492,7 @@ MA_API float ma_fader_get_current_volume(const ma_fader* pFader)
|
|||||||
/* The current volume depends on the position of the cursor. */
|
/* The current volume depends on the position of the cursor. */
|
||||||
if (pFader->cursorInFrames == 0) {
|
if (pFader->cursorInFrames == 0) {
|
||||||
return pFader->volumeBeg;
|
return pFader->volumeBeg;
|
||||||
} else if (pFader->cursorInFrames >= pFader->lengthInFrames) {
|
} else if ((ma_uint64)pFader->cursorInFrames >= pFader->lengthInFrames) { /* Safe case because the < 0 case was checked above. */
|
||||||
return pFader->volumeEnd;
|
return pFader->volumeEnd;
|
||||||
} else {
|
} else {
|
||||||
/* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */
|
/* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */
|
||||||
@@ -74160,6 +74173,17 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
|
|||||||
totalFramesProcessedIn = 0;
|
totalFramesProcessedIn = 0;
|
||||||
totalFramesProcessedOut = 0;
|
totalFramesProcessedOut = 0;
|
||||||
|
|
||||||
|
/* Update the fader if applicable. */
|
||||||
|
{
|
||||||
|
ma_uint64 fadeLengthInFrames = ma_atomic_uint64_get(&pEngineNode->fadeSettings.fadeLengthInFrames);
|
||||||
|
if (fadeLengthInFrames != ~(ma_uint64)0) {
|
||||||
|
ma_fader_set_fade_ex(&pEngineNode->fader, ma_atomic_float_get(&pEngineNode->fadeSettings.volumeBeg), ma_atomic_float_get(&pEngineNode->fadeSettings.volumeEnd), fadeLengthInFrames, ma_atomic_uint64_get(&pEngineNode->fadeSettings.absoluteGlobalTimeInFrames) - ma_engine_get_time(pEngineNode->pEngine));
|
||||||
|
|
||||||
|
/* Reset the fade length so we don't erroneously apply it again. */
|
||||||
|
ma_atomic_uint64_set(&pEngineNode->fadeSettings.fadeLengthInFrames, ~(ma_uint64)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);
|
||||||
@@ -74178,10 +74202,10 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo
|
|||||||
the output buffer and then do all effects from that point directly in the output buffer
|
the output buffer and then do all effects from that point directly in the output buffer
|
||||||
in-place.
|
in-place.
|
||||||
|
|
||||||
Note that we're always running the resampler. If we try to be clever and skip resampling
|
Note that we're always running the resampler if pitching is enabled, even when the pitch
|
||||||
when the pitch is 1, we'll get a glitch when we move away from 1, back to 1, and then
|
is 1. If we try to be clever and skip resampling when the pitch is 1, we'll get a glitch
|
||||||
away from 1 again. We'll want to implement any pitch=1 optimizations in the resampler
|
when we move away from 1, back to 1, and then away from 1 again. We'll want to implement
|
||||||
itself.
|
any pitch=1 optimizations in the resampler itself.
|
||||||
|
|
||||||
There's a small optimization here that we'll utilize since it might be a fairly common
|
There's a small optimization here that we'll utilize since it might be a fairly common
|
||||||
case. When the input and output channel counts are the same, we'll read straight into the
|
case. When the input and output channel counts are the same, we'll read straight into the
|
||||||
@@ -74679,6 +74703,10 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p
|
|||||||
pEngineNode->isPitchDisabled = pConfig->isPitchDisabled;
|
pEngineNode->isPitchDisabled = pConfig->isPitchDisabled;
|
||||||
pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled;
|
pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled;
|
||||||
pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex;
|
pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex;
|
||||||
|
ma_atomic_float_set(&pEngineNode->fadeSettings.volumeBeg, 1);
|
||||||
|
ma_atomic_float_set(&pEngineNode->fadeSettings.volumeEnd, 1);
|
||||||
|
ma_atomic_uint64_set(&pEngineNode->fadeSettings.fadeLengthInFrames, (~(ma_uint64)0));
|
||||||
|
ma_atomic_uint64_set(&pEngineNode->fadeSettings.absoluteGlobalTimeInFrames, (~(ma_uint64)0)); /* <-- Indicates that the fade should start immediately. */
|
||||||
|
|
||||||
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);
|
||||||
@@ -76546,7 +76574,7 @@ MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, f
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ma_fader_set_fade(&pSound->engineNode.fader, volumeBeg, volumeEnd, fadeLengthInFrames);
|
ma_sound_set_fade_start_in_pcm_frames(pSound, volumeBeg, volumeEnd, fadeLengthInFrames, (~(ma_uint64)0));
|
||||||
}
|
}
|
||||||
|
|
||||||
MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds)
|
MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds)
|
||||||
@@ -76558,6 +76586,36 @@ MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg,
|
|||||||
ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000);
|
ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MA_API void ma_sound_set_fade_start_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames, ma_uint64 absoluteGlobalTimeInFrames)
|
||||||
|
{
|
||||||
|
if (pSound == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We don't want to update the fader at this point because we need to use the engine's current time
|
||||||
|
to derive the fader's start offset. The timer is being updated on the audio thread so in order to
|
||||||
|
do this as accurately as possible we'll need to defer this to the audio thread.
|
||||||
|
*/
|
||||||
|
ma_atomic_float_set(&pSound->engineNode.fadeSettings.volumeBeg, volumeBeg);
|
||||||
|
ma_atomic_float_set(&pSound->engineNode.fadeSettings.volumeEnd, volumeEnd);
|
||||||
|
ma_atomic_uint64_set(&pSound->engineNode.fadeSettings.fadeLengthInFrames, fadeLengthInFrames);
|
||||||
|
ma_atomic_uint64_set(&pSound->engineNode.fadeSettings.absoluteGlobalTimeInFrames, absoluteGlobalTimeInFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
MA_API void ma_sound_set_fade_start_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds, ma_uint64 absoluteGlobalTimeInMilliseconds)
|
||||||
|
{
|
||||||
|
ma_uint32 sampleRate;
|
||||||
|
|
||||||
|
if (pSound == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleRate = ma_engine_get_sample_rate(ma_sound_get_engine(pSound));
|
||||||
|
|
||||||
|
ma_sound_set_fade_start_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * sampleRate) / 1000, (absoluteGlobalTimeInMilliseconds * sampleRate) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
MA_API float ma_sound_get_current_fade_volume(const ma_sound* pSound)
|
MA_API float ma_sound_get_current_fade_volume(const ma_sound* pSound)
|
||||||
{
|
{
|
||||||
if (pSound == NULL) {
|
if (pSound == NULL) {
|
||||||
|
|||||||
Reference in New Issue
Block a user