This will be called if either there is no pre-effect nor pitch shift, or the pre-effect and pitch shift have already been processed. In this case it's allowed for
pFramesIn to be equal to pFramesOut as from here on we support in-place processing. Also, the input and output frame counts should always be equal.
/* If the current time on the fader has past the end we don't need to keep running it. We just need to ensure that when we reboot it that we set the current time properly. */
/* This function is only safe when the sound is not flagged as playing. */
MA_ASSERT(pSound->isPlaying==MA_FALSE);
if(isFading){
/* Fast path. No fading. */
}else{
/* Slow path. Fading required. The spatialization processed moved data into the output buffer so we need to do this in-place. */
ma_fader_process_pcm_frames(&pEngineEffect->fader,pFramesOut,pFramesOut,frameCount);/* <-- Intentionally set both input and output buffers to pFramesOut because we want this to be in-place. */
/* Fast path. No spatialization. Don't do anything - the panning step above moved data into the output buffer for us. */
}else{
/* Slow path. Spatialization required. Note that we just panned which means the output buffer currently contains valid data. We can spatialize in-place. */
ma_spatializer_process_pcm_frames(&pEngineEffect->spatializer,pFramesOut,pFramesOut,frameCount);/* <-- Intentionally set both input and output buffers to pFramesOut because we want this to be in-place. */
}
if(isFading){
/* Fast path. No fading. */
}else{
/* Slow path. Fading required. */
ma_fader_process_pcm_frames(&pEngineEffect->fader,pFramesOut,pFramesOut,frameCount);/* <-- Intentionally set both input and output buffers to pFramesOut because we want this to be in-place. */
If the sound is muted we still need to move time forward, but we can save time by not mixing as it won't actually affect anything. If there's an
effect we need to make sure we run it through the mixer because it may require us to update internal state for things like echo effects.
*/
/* TODO: ma_engine_effect_is_passthrough(). */
/*
This will be called if either there is no pre-effect or the pre-effect has already been processed. We can safely assume the input and output data in the engine's format so no
/* The sound is muted. We want to move time forward, but it be made faster by simply seeking instead of reading. We also want to bypass mixing completely. */
We can output straight into the output buffer. The remaining effects support in-place processing so when we process those we'll just pass in the output buffer
as the input buffer as well and the effect will operate on the buffer in-place.
*/
ma_resultresult;
ma_uint64frameCountIn;
ma_uint64frameCountOut;
/* If we reached the end of the sound we'll want to mark it as at the end and not playing. */
if(result==MA_AT_END){
ma_engine_sound_stop_internal(pEngine,pSound);
c89atomic_exchange_32(&pSound->atEnd,MA_TRUE);/* Set to false in ma_engine_sound_start(). */
/* Here is where we want to apply the remaining effects. These can be processed in-place which means we want to set the input and output buffers to be the same. */
frameCountIn=*pFrameCountOut;/* Not a mistake. Intentionally set to *pFrameCountOut. */
frameCountOut=*pFrameCountOut;
returnma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(pEngineEffect,pFramesOut,&frameCountIn,pFramesOut,&frameCountOut);/* Intentionally setting the input buffer to pFramesOut for in-place processing. */
/* The effect's input and output format will be the engine's format. If the pre-effect is of a different format it will need to be converted appropriately. */
effectFormat=pEngineEffect->pEngine->format;
effectChannels=pEngineEffect->pEngine->channels;
/*
Getting here means we have a pre-effect. This must alway be run first. We do this in chunks into an intermediary buffer and then call ma_engine_effect__on_process_pcm_frames__no_pre_effect()
against the intermediary buffer. The output of ma_engine_effect__on_process_pcm_frames__no_pre_effect() will be the final output buffer.
deviceConfig.noPreZeroedOutputBuffer=MA_TRUE;/* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */
deviceConfig.noClip=MA_TRUE;/* The mixing engine here will do clipping for us. */
/* With the device initialized we need an intermediary buffer for handling fixed sized updates. Currently using a ring buffer for this, but can probably use something a bit more optimal. */
/* The config is allowed to be NULL in which case we use defaults for everything. */
if(pConfig!=NULL){
engineConfig=*pConfig;
}else{
engineConfig=ma_engine_config_init_default();
}
MA_ASSERT(pEngine!=NULL);
MA_ASSERT(pEffect!=NULL);
/*
For now we only support f32 but may add support for other formats later. To do this we need to add support for all formats to ma_panner and ma_spatializer (and any other future effects).
returnresult;/* Failed to create the spatializer. */
}
/* With the context create we can now create the default listener. After we have the listener we can set the format, channels and sample rate appropriately. */
returnresult;/* Failed to initialize default listener. */
returnresult;/* Failed to create the fader. */
}
/* Now that have the default listener we can ensure we have the format, channels and sample rate set to proper values to ensure future listeners are configured consistently. */
TODO: A few things to figure out with the resampler:
- In order to support dynamic pitch shifting we need to set allowDynamicSampleRate which means the resampler will always be initialized and will always
have samples run through it. An optimization would be to have a flag that disables pitch shifting. Can alternatively just skip running samples through
the data converter when pitch=1, but this may result in glitching when moving away from pitch=1 due to the internal buffer not being update while the
pitch=1 case was in place.
- We may want to have customization over resampling properties.
*/
converterConfig.resampling.allowDynamicSampleRate=MA_TRUE;/* This makes sure a resampler is always initialized. TODO: Need a flag that specifies that no pitch shifting is required for this sound so we can avoid the cost of the resampler. Even when the pitch is 1, samples still run through the resampler. */
/* The sound is muted. We want to move time forward, but it be made faster by simply seeking instead of reading. We also want to bypass mixing completely. */
/* We need to do updates in fixed sizes based on the engine's period size in frames. */
/*
The sound should never be in a playing state when this is called. It *can*, however, but in the middle of mixing in the mixing thread. It needs to finish
mixing before being uninitialized completely, but that is done at a higher level to this function.
The first thing to do is check if there's enough data available in the ring buffer. If so we can read from it. Otherwise we need to keep filling
the ring buffer until there's enough, making sure we only fill the ring buffer in chunks of pEngine->periodSizeInFrames.
*/
MA_ASSERT(pSound->isPlaying==MA_FALSE);
while(pcmFramesProcessed<frameCount){/* Keep going until we've filled the output buffer. */
We want the creation and deletion of sounds to be supported across multiple threads. An application may have any thread that want's to call
ma_engine_play_sound(), for example. The application would expect this to just work. The problem, however, is that the mixing thread will be iterating over
the list at the same time. We need to be careful with how we remove a sound from the list because we'll essentially be taking the sound out from under the
mixing thread and the mixing thread must continue to work. Normally you would wrap the iteration in a lock as well, however an added complication is that
the mixing thread cannot be locked as it's running on the audio thread, and locking in the audio thread is a no-no).
To start with, ma_engine_sound_detach() (this function) and ma_engine_sound_attach() need to be wrapped in a lock. This lock will *not* be used by the
mixing thread. We therefore need to craft this in a very particular way so as to ensure the mixing thread does not lose track of it's iteration state. What
we don't want to do is clear the pNextSoundInGroup variable to NULL. This need to be maintained to ensure the mixing thread can continue iteration even
after the sound has been removed from the group. This is acceptable because sounds are fixed to their group for the entire life, and this function will
only ever be called when the sound is being uninitialized which therefore means it'll never be iterated again.
*/
ma_mutex_lock(&pGroup->lock);
{
if(pSound->pPrevSoundInGroup==NULL){
/* The sound is the head of the list. All we need to do is change the pPrevSoundInGroup member of the next sound to NULL and make it the new head. */
deviceConfig.noPreZeroedOutputBuffer=MA_TRUE;/* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */
deviceConfig.noClip=MA_TRUE;/* The mixing engine here will do clipping for us. */
/* With the device initialized we need an intermediary buffer for handling fixed sized updates. Currently using a ring buffer for this, but can probably use something a bit more optimal. */
This will be called if either there is no pre-effect nor pitch shift, or the pre-effect and pitch shift have already been processed. In this case it's allowed for
pFramesIn to be equal to pFramesOut as from here on we support in-place processing. Also, the input and output frame counts should always be equal.
/* If the current time on the fader has past the end we don't need to keep running it. We just need to ensure that when we reboot it that we set the current time properly. */
/* Slow path. Fading required. The spatialization processed moved data into the output buffer so we need to do this in-place. */
ma_fader_process_pcm_frames(&pEngineEffect->fader,pFramesOut,pFramesOut,frameCount);/* <-- Intentionally set both input and output buffers to pFramesOut because we want this to be in-place. */
/* Fast path. No spatialization. Don't do anything - the panning step above moved data into the output buffer for us. */
}else{
/* Slow path. Spatialization required. Note that we just panned which means the output buffer currently contains valid data. We can spatialize in-place. */
ma_spatializer_process_pcm_frames(&pEngineEffect->spatializer,pFramesOut,pFramesOut,frameCount);/* <-- Intentionally set both input and output buffers to pFramesOut because we want this to be in-place. */
}
/* The config is allowed to be NULL in which case we use defaults for everything. */
if(pConfig!=NULL){
engineConfig=*pConfig;
}else{
engineConfig=ma_engine_config_init_default();
}
if(isFading){
/* Fast path. No fading. */
}else{
/* Slow path. Fading required. */
ma_fader_process_pcm_frames(&pEngineEffect->fader,pFramesOut,pFramesOut,frameCount);/* <-- Intentionally set both input and output buffers to pFramesOut because we want this to be in-place. */
}
/*
For now we only support f32 but may add support for other formats later. To do this we need to add support for all formats to ma_panner and ma_spatializer (and any other future effects).
This will be called if either there is no pre-effect or the pre-effect has already been processed. We can safely assume the input and output data in the engine's format so no
/* Slow path. Pitch shifting required. We need to run everything through our data converter first. */
/* With the context create we can now create the default listener. After we have the listener we can set the format, channels and sample rate appropriately. */
returnresult;/* Failed to initialize default listener. */
}
/*
We can output straight into the output buffer. The remaining effects support in-place processing so when we process those we'll just pass in the output buffer
as the input buffer as well and the effect will operate on the buffer in-place.
*/
ma_resultresult;
ma_uint64frameCountIn;
ma_uint64frameCountOut;
/* Now that have the default listener we can ensure we have the format, channels and sample rate set to proper values to ensure future listeners are configured consistently. */
/* Here is where we want to apply the remaining effects. These can be processed in-place which means we want to set the input and output buffers to be the same. */
frameCountIn=*pFrameCountOut;/* Not a mistake. Intentionally set to *pFrameCountOut. */
frameCountOut=*pFrameCountOut;
returnma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(pEngineEffect,pFramesOut,&frameCountIn,pFramesOut,&frameCountOut);/* Intentionally setting the input buffer to pFramesOut for in-place processing. */
/* We need a default sound group. This must be done after setting the format, channels and sample rate to their proper values. */
/* The effect's input and output format will be the engine's format. If the pre-effect is of a different format it will need to be converted appropriately. */
effectFormat=pEngineEffect->pEngine->format;
effectChannels=pEngineEffect->pEngine->channels;
/*
Getting here means we have a pre-effect. This must alway be run first. We do this in chunks into an intermediary buffer and then call ma_engine_effect__on_process_pcm_frames__no_pre_effect()
against the intermediary buffer. The output of ma_engine_effect__on_process_pcm_frames__no_pre_effect() will be the final output buffer.
The sound should never be in a playing state when this is called. It *can*, however, but in the middle of mixing in the mixing thread. It needs to finish
mixing before being uninitialized completely, but that is done at a higher level to this function.
*/
MA_ASSERT(pSound->isPlaying==MA_FALSE);
pEffect->pEngine=pEngine;
pEffect->pPreEffect=NULL;
pEffect->pitch=1;
pEffect->oldPitch=1;
/*
We want the creation and deletion of sounds to be supported across multiple threads. An application may have any thread that want's to call
ma_engine_play_sound(), for example. The application would expect this to just work. The problem, however, is that the mixing thread will be iterating over
the list at the same time. We need to be careful with how we remove a sound from the list because we'll essentially be taking the sound out from under the
mixing thread and the mixing thread must continue to work. Normally you would wrap the iteration in a lock as well, however an added complication is that
the mixing thread cannot be locked as it's running on the audio thread, and locking in the audio thread is a no-no).
To start with, ma_engine_sound_detach() (this function) and ma_engine_sound_attach() need to be wrapped in a lock. This lock will *not* be used by the
mixing thread. We therefore need to craft this in a very particular way so as to ensure the mixing thread does not lose track of it's iteration state. What
we don't want to do is clear the pNextSoundInGroup variable to NULL. This need to be maintained to ensure the mixing thread can continue iteration even
after the sound has been removed from the group. This is acceptable because sounds are fixed to their group for the entire life, and this function will
only ever be called when the sound is being uninitialized which therefore means it'll never be iterated again.
*/
ma_mutex_lock(&pGroup->lock);
{
if(pSound->pPrevSoundInGroup==NULL){
/* The sound is the head of the list. All we need to do is change the pPrevSoundInGroup member of the next sound to NULL and make it the new head. */
/* This should only ever be called when the sound is first initialized which means we should never be in a playing state. */
MA_ASSERT(pSound->isPlaying==MA_FALSE);
/* We can set the group at the start. */
pSound->pGroup=pGroup;
/*
TODO: A few things to figure out with the resampler:
- In order to support dynamic pitch shifting we need to set allowDynamicSampleRate which means the resampler will always be initialized and will always
have samples run through it. An optimization would be to have a flag that disables pitch shifting. Can alternatively just skip running samples through
the data converter when pitch=1, but this may result in glitching when moving away from pitch=1 due to the internal buffer not being update while the
pitch=1 case was in place.
- We may want to have customization over resampling properties.
The sound will become the new head of the list. If we were only adding we could do this lock-free, but unfortunately we need to support fast, constant
time removal of sounds from the list. This means we need to update two pointers, not just one, which means we can't use a standard compare-and-swap.
One of our requirements is that the mixer thread must be able to iterate over the list *without* locking. We don't really need to do anything special
here to support this, but we will want to use an atomic assignment.
*/
converterConfig.resampling.allowDynamicSampleRate=MA_TRUE;/* This makes sure a resampler is always initialized. TODO: Need a flag that specifies that no pitch shifting is required for this sound so we can avoid the cost of the resampler. Even when the pitch is 1, samples still run through the resampler. */