ma_engine_nodeengineNode;/* Must be the first member for compatibility with the ma_node API. */
ma_engine_nodeengineNode;/* Must be the first member for compatibility with the ma_node API. */
ma_data_source*pDataSource;
ma_data_source*pDataSource;
ma_sound_group*pGroup;/* The group the sound is attached to. */
volatilema_sound*pPrevSoundInGroup;
volatilema_sound*pNextSoundInGroup;
floatvolume;
ma_uint64seekTarget;/* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
ma_uint64seekTarget;/* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
ma_uint64runningTimeInEngineFrames;/* The amount of time the sound has been running in engine frames, including start delays. */
ma_uint64runningTimeInEngineFrames;/* The amount of time the sound has been running in engine frames, including start delays. */
ma_uint64startDelayInEngineFrames;/* In the engine's sample rate. */
ma_uint64startDelayInEngineFrames;/* In the engine's sample rate. */
ma_uint64stopDelayInEngineFrames;/* In the engine's sample rate. */
ma_uint64stopDelayInEngineFrames;/* In the engine's sample rate. */
ma_uint64stopDelayInEngineFramesRemaining;/* The number of frames relative to the engine's clock before the sound is stopped. */
ma_uint64stopDelayInEngineFramesRemaining;/* The number of frames relative to the engine's clock before the sound is stopped. */
volatilema_bool32isPlaying;/* False by default. Sounds need to be explicitly started with ma_sound_start() and stopped with ma_sound_stop(). */
volatilema_bool32isMixing;
volatilema_bool32isLooping;/* False by default. */
volatilema_bool32isLooping;/* False by default. */
volatilema_bool32atEnd;
volatilema_bool32atEnd;
ma_bool32ownsDataSource;
ma_bool32ownsDataSource;
...
@@ -2207,19 +2201,10 @@ struct ma_sound
...
@@ -2207,19 +2201,10 @@ struct ma_sound
structma_sound_group
structma_sound_group
{
{
ma_engine_nodeengineNode;/* Must be the first member for compatibility with the ma_node API. */
ma_engine_nodeengineNode;/* Must be the first member for compatibility with the ma_node API. */
ma_sound_group*pParent;
ma_sound_group*pFirstChild;
ma_sound_group*pPrevSibling;
ma_sound_group*pNextSibling;
volatilema_sound*pFirstSoundInGroup;
ma_engine_effecteffect;/* The main effect for panning, etc. This is set on the mixer at initialisation time. */
ma_mixermixer;
ma_mutexlock;/* Only used by ma_sound_init_*() and ma_sound_uninit(). Not used in the mixing thread. */
ma_uint64runningTimeInEngineFrames;/* The amount of time the sound has been running in engine frames, including start delays. */
ma_uint64runningTimeInEngineFrames;/* The amount of time the sound has been running in engine frames, including start delays. */
ma_uint64startDelayInEngineFrames;
ma_uint64startDelayInEngineFrames;
ma_uint64stopDelayInEngineFrames;/* In the engine's sample rate. */
ma_uint64stopDelayInEngineFrames;/* In the engine's sample rate. */
ma_uint64stopDelayInEngineFramesRemaining;/* The number of frames relative to the engine's clock before the sound is stopped. */
ma_uint64stopDelayInEngineFramesRemaining;/* The number of frames relative to the engine's clock before the sound is stopped. */
volatilema_bool32isPlaying;/* True by default. Sound groups can be stopped with ma_sound_stop() and resumed with ma_sound_start(). Also affects children. */
};
};
structma_listener
structma_listener
...
@@ -2254,7 +2239,6 @@ struct ma_engine
...
@@ -2254,7 +2239,6 @@ struct ma_engine
ma_device*pDevice;/* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
ma_device*pDevice;/* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
ma_pcm_rbfixedRB;/* The intermediary ring buffer for helping with fixed sized updates. */
ma_pcm_rbfixedRB;/* The intermediary ring buffer for helping with fixed sized updates. */
ma_listenerlistener;
ma_listenerlistener;
ma_sound_groupmasterSoundGroup;/* Sounds are associated with this group by default. */
/* We need to loop here to ensure we fill every frame. This won't necessarily be able to be done in one iteration due to resampling within the effect. */
/* 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. */
/* 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. */
/* 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. */
/* 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. */
/* Make sure the node graph is uninitialized after the audio thread has been shutdown to prevent accessing of the node graph after being uninitialized. */
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(ma_sound_is_playing(pSound)==MA_FALSE);
/*
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_sound_detach() (this function) and ma_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.
#if 1 /* TODO: Delete this whole section later. */
/* Make sure the sound is stopped as soon as possible to reduce the chance that it gets locked by the mixer. We also need to stop it before detaching from the group. */
ma_sound_set_stop_delay(pSound,0);/* <-- Ensures the sound stops immediately. */
result=ma_sound_stop(pSound);
if(result!=MA_SUCCESS){
return;
}
/* The sound needs to removed from the group to ensure it doesn't get iterated again and cause things to break again. This is thread-safe. */
result=ma_sound_detach(pSound);
if(result!=MA_SUCCESS){
return;
}
/*
The sound is detached from the group, but it may still be in the middle of mixing which means our data source is locked. We need to wait for
this to finish before deleting from the resource manager.
We could define this so that we don't wait if the sound does not own the underlying data source, but this might end up being dangerous because
the application may think it's safe to destroy the data source when it actually isn't. It just feels untidy doing it like that.
*/
ma_sound_mix_wait(pSound);
#endif
/* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */
/* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */
/* TODO: Look at the possibility of allowing groups to use a different format to the primary data format. Makes mixing and group management much more complicated. */
/* For handling panning, etc. we'll need an engine effect. */
MA_ASSERT(pGroup==&pEngine->masterSoundGroup);/* The master group is the only one allowed to not have a parent group. */
}
/*
We need to initialize the lock that'll be used to synchronize adding and removing of sounds to the group. This lock is _not_ used by the mixing thread. The mixing
thread is written in a way where a lock should not be required.
*/
result=ma_mutex_init(&pGroup->lock);
if(result!=MA_SUCCESS){
ma_sound_group_detach(pGroup);
ma_mixer_uninit(&pGroup->mixer);
ma_engine_effect_uninit(pEngine,&pGroup->effect);
returnresult;
}
/* The group needs to be started by default, but needs to be done after attaching to the internal list. */
ma_sound_group_set_stop_delay(pGroup,0);/* <-- Make sure we disable fading out so the sound group is stopped immediately. */
result=ma_sound_group_stop(pGroup);
if(result!=MA_SUCCESS){
MA_ASSERT(MA_FALSE);/* Should never happen. Trigger an assert for debugging, but don't stop uninitializing in production to ensure we free memory down below. */
}
/* Any in-place sounds need to be uninitialized. */