Commit d5b25502 authored by David Reid's avatar David Reid

Migrate start and stop delays to the node graph system.

This changes ma_sound_set_start/stop_delay() to take an absolute time
in frames based on the global clock. Previously these took a relative
time in milliseconds. To use a relative time, add it to the value
returned by ma_engine_get_time(). To use milliseconds, use a standard
sample rate to milliseconds conversion.
parent a6cd9340
......@@ -115,6 +115,7 @@ int main(int argc, char** argv)
//ma_sound_set_fade_point_auto_reset(&sound, 1, MA_FALSE);
//ma_sound_set_stop_delay(&sound, 1000);
//ma_sound_set_volume(&sound, 1);
//ma_sound_set_start_delay(&sound, 48000);
ma_sound_start(&sound);
//ma_sleep(1000);
......
......@@ -891,7 +891,7 @@ struct ma_node_graph
/* Read and written by multiple threads. */
volatile ma_uint32 readCounter; /* Nodes spin on this while they wait for reading for finish before returning from ma_node_uninit(). */
volatile ma_uint8 isReading;
volatile ma_bool8 isReading;
};
MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph);
......@@ -1575,7 +1575,7 @@ typedef struct
{
ma_engine* pEngine;
ma_engine_node_type type;
ma_uint32 channels;
ma_uint32 channels; /* Only used when the type is set to ma_engine_node_type_sound. */
} ma_engine_node_config;
MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type);
......@@ -1592,7 +1592,7 @@ typedef struct
MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode);
MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks);
MA_API ma_result ma_engine_node_reset(ma_engine_node* pEngineNode); /* This is used when sounds are recycled from ma_engine_play_sound(). */
MA_API ma_result ma_engine_node_set_time(ma_engine_node* pEngineNode, ma_uint64 timeInFrames);
MA_API ma_result ma_engine_node_set_time(ma_engine_node* pEngineNode, ma_uint64 timeInFrames); /* TODO: Remove this when the engine effect has been refactored. Use ma_node_set/get_time() instead. */
struct ma_sound
......@@ -1600,24 +1600,32 @@ struct ma_sound
ma_engine_node engineNode; /* Must be the first member for compatibility with the ma_node API. */
ma_data_source* pDataSource;
ma_uint64 seekTarget; /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */
ma_uint64 runningTimeInEngineFrames; /* The amount of time the sound has been running in engine frames, including start delays. */
ma_uint64 startDelayInEngineFrames; /* In the engine's sample rate. */
ma_uint64 stopDelayInEngineFrames; /* In the engine's sample rate. */
ma_uint64 stopDelayInEngineFramesRemaining; /* The number of frames relative to the engine's clock before the sound is stopped. */
volatile ma_bool32 isLooping; /* False by default. */
volatile ma_bool32 atEnd;
ma_bool32 ownsDataSource;
ma_bool32 _isInternal; /* A marker to indicate the sound is managed entirely by the engine. This will be set to true when the sound is created internally by ma_engine_play_sound(). */
volatile ma_bool8 isLooping; /* False by default. */
volatile ma_bool8 atEnd;
ma_bool8 ownsDataSource;
ma_bool8 _isInternal; /* A marker to indicate the sound is managed entirely by the engine. This will be set to true when the sound is created internally by ma_engine_play_sound(). */
/*
We're declaring a resource manager data source object here to save us a malloc when loading a
sound via the resource manager, which I *think* will be the most common scenario.
*/
#ifndef MA_NO_RESOURCE_MANAGER
ma_resource_manager_data_source resourceManagerDataSource;
#endif
};
/* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */
typedef struct ma_sound_inlined ma_sound_inlined;
struct ma_sound_inlined
{
ma_sound sound;
volatile ma_sound_inlined* pNext; /* Can be modified by multiple threads. */
volatile ma_sound_inlined* pPrev; /* Can be modified by multiple threads. */
};
struct ma_sound_group
{
ma_engine_node engineNode; /* Must be the first member for compatibility with the ma_node API. */
ma_uint64 runningTimeInEngineFrames; /* The amount of time the sound has been running in engine frames, including start delays. */
ma_uint64 startDelayInEngineFrames;
ma_uint64 stopDelayInEngineFrames; /* In the engine's sample rate. */
ma_uint64 stopDelayInEngineFramesRemaining; /* The number of frames relative to the engine's clock before the sound is stopped. */
};
struct ma_listener
......@@ -1650,12 +1658,12 @@ struct ma_engine
ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */
ma_resource_manager* pResourceManager;
ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */
ma_pcm_rb fixedRB; /* The intermediary ring buffer for helping with fixed sized updates. */
ma_pcm_rb fixedRB; /* The intermediary ring buffer for helping with fixed sized updates. */ /* TODO: Is fixed sized updates required? */
ma_listener listener;
ma_uint32 channels;
ma_uint32 sampleRate;
ma_uint32 periodSizeInFrames;
ma_uint32 periodSizeInMilliseconds;
ma_uint32 channels; /* TODO: Get this from the node graph? */
ma_uint32 sampleRate; /* TODO: Get this from the device? Is this needed when supporting non-device engines? */
ma_uint32 periodSizeInFrames; /* TODO: Is this needed? */
ma_uint32 periodSizeInMilliseconds; /* TODO: Is this needed? */
ma_allocation_callbacks allocationCallbacks;
ma_bool8 ownsResourceManager;
ma_bool8 ownsDevice;
......@@ -1663,15 +1671,16 @@ struct ma_engine
MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine);
MA_API void ma_engine_uninit(ma_engine* pEngine);
MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead);
MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pOutput, const void* pInput, ma_uint32 frameCount);
MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine);
MA_API ma_uint64 ma_engine_get_time(ma_engine* pEngine);
MA_API ma_result ma_engine_start(ma_engine* pEngine);
MA_API ma_result ma_engine_stop(ma_engine* pEngine);
MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume);
MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB);
MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine);
MA_API ma_result ma_engine_listener_set_position(ma_engine* pEngine, ma_vec3 position);
MA_API ma_result ma_engine_listener_set_rotation(ma_engine* pEngine, ma_quat rotation);
......@@ -1692,13 +1701,13 @@ MA_API ma_result ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode pan_mode);
MA_API ma_result ma_sound_set_pitch(ma_sound* pSound, float pitch);
MA_API ma_result ma_sound_set_position(ma_sound* pSound, ma_vec3 position);
MA_API ma_result ma_sound_set_rotation(ma_sound* pSound, ma_quat rotation);
MA_API ma_result ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping);
MA_API ma_result ma_sound_set_looping(ma_sound* pSound, ma_bool8 isLooping);
MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound);
MA_API ma_result ma_sound_set_fade_in_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames);
MA_API ma_result ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds);
MA_API ma_result ma_sound_get_current_fade_volume(ma_sound* pSound, float* pVolume);
MA_API ma_result ma_sound_set_start_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds);
MA_API ma_result ma_sound_set_stop_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds);
MA_API ma_result ma_sound_set_start_delay(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames);
MA_API ma_result ma_sound_set_stop_delay(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames);
MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound);
MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound);
MA_API ma_result ma_sound_get_time_in_frames(const ma_sound* pSound, ma_uint64* pTimeInFrames);
......@@ -1718,8 +1727,8 @@ MA_API ma_result ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch);
MA_API ma_result ma_sound_group_set_fade_in_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames);
MA_API ma_result ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds);
MA_API ma_result ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup, float* pVolume);
MA_API ma_result ma_sound_group_set_start_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds);
MA_API ma_result ma_sound_group_set_stop_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds);
MA_API ma_result ma_sound_group_set_start_delay(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames);
MA_API ma_result ma_sound_group_set_stop_delay(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames);
MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup);
MA_API ma_result ma_sound_group_get_time_in_frames(const ma_sound_group* pGroup, ma_uint64* pTimeInFrames);
......@@ -9086,24 +9095,12 @@ void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, float** ppFramesOu
ma_data_source_seek_to_pcm_frame(pSound->pDataSource, pSound->seekTarget);
/* Any time-dependant effects need to have their times updated. */
ma_engine_node_set_time(&pSound->engineNode, pSound->seekTarget);
ma_engine_node_set_time(&pSound->engineNode, pSound->seekTarget); /* TODO: Delete this line. */
ma_node_set_time(pSound, pSound->seekTarget);
pSound->seekTarget = MA_SEEK_TARGET_NONE;
}
/* If the sound is being delayed we don't want to mix anything, nor do we want to advance time forward from the perspective of the data source. */
if ((pSound->runningTimeInEngineFrames + frameCount) > pSound->startDelayInEngineFrames) {
/* We're not delayed so we can mix or seek. In order to get frame-exact playback timing we need to start mixing from an offset. */
ma_uint64 currentTimeInFrames;
ma_uint64 offsetInFrames;
offsetInFrames = 0;
if (pSound->startDelayInEngineFrames > pSound->runningTimeInEngineFrames) {
offsetInFrames = pSound->startDelayInEngineFrames - pSound->runningTimeInEngineFrames;
}
MA_ASSERT(offsetInFrames < frameCount);
/*
For the convenience of the caller, we're doing to allow data sources to use non-floating-point formats and channel counts that differ
from the main engine.
......@@ -9135,7 +9132,7 @@ void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, float** ppFramesOu
/* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */
if (result == MA_AT_END) {
c89atomic_exchange_32(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */
c89atomic_exchange_8(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */
}
pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, pSound->engineNode.pEngine->channels);
......@@ -9159,40 +9156,20 @@ void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, float** ppFramesOu
}
/* We should have processed all of our input frames since we calculated the required number of input frames at the top. */
//MA_ASSERT(frameCountIn == framesJustRead);
MA_ASSERT(frameCountIn == framesJustRead);
totalFramesRead += (ma_uint32)frameCountOut; /* Safe cast. */
/*
For the benefit of the main effect we need to ensure the local time is updated explicitly. This is required for allowing time-based effects to
support loop transitions properly.
support loop transitions properly. This is temporary until we migrate the engine effect over to the ma_node timing system.
*/
result = ma_sound_get_cursor_in_pcm_frames(pSound, &currentTimeInFrames);
if (result == MA_SUCCESS) {
ma_engine_node_set_time(&pSound->engineNode, currentTimeInFrames);
}
pSound->runningTimeInEngineFrames += offsetInFrames + totalFramesRead;
ma_engine_node_set_time(&pSound->engineNode, ma_node_get_time(pSound));
if (result != MA_SUCCESS || ma_sound_at_end(pSound)) {
break; /* Might have reached the end. */
}
}
}
}
/* If we're stopping after a delay we need to check if the delay has expired and if so, stop for real. */
if (pSound->stopDelayInEngineFramesRemaining > 0) {
if (pSound->stopDelayInEngineFramesRemaining >= totalFramesRead) {
pSound->stopDelayInEngineFramesRemaining -= totalFramesRead;
} else {
pSound->stopDelayInEngineFramesRemaining = 0;
}
/* Stop the sound if the delay has been reached. */
if (pSound->stopDelayInEngineFramesRemaining == 0) {
ma_sound_stop(pSound);
}
}
/* The effect needs to be updated once per-processing, and must be at the end. */
ma_engine_effect__update_resampler_for_pitching(&pSound->engineNode.effect);
......@@ -9325,7 +9302,7 @@ static void ma_engine_listener__data_callback_fixed(ma_engine* pEngine, void* pF
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEngine->periodSizeInFrames == frameCount); /* This must always be true. */
ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, NULL);
ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL);
}
static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
......@@ -9498,6 +9475,11 @@ MA_API void ma_engine_uninit(ma_engine* pEngine)
#endif
}
MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead)
{
return ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, pFramesRead);
}
MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{
ma_uint32 pcmFramesAvailableInRB;
......@@ -9553,6 +9535,16 @@ MA_API void ma_engine_data_callback(ma_engine* pEngine, void* pFramesOut, const
(void)pFramesIn; /* Not doing anything with input right now. */
}
MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine)
{
return ma_node_graph_get_endpoint(&pEngine->nodeGraph);
}
MA_API ma_uint64 ma_engine_get_time(ma_engine* pEngine)
{
return ma_node_graph_get_time(&pEngine->nodeGraph);
}
MA_API ma_result ma_engine_start(ma_engine* pEngine)
{
......@@ -9605,12 +9597,6 @@ MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB)
}
MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine)
{
return ma_node_graph_get_endpoint(&pEngine->nodeGraph);
}
MA_API ma_result ma_engine_listener_set_position(ma_engine* pEngine, ma_vec3 position)
{
if (pEngine == NULL) {
......@@ -9940,7 +9926,7 @@ MA_API ma_result ma_sound_start(ma_sound* pSound)
}
/* Make sure we clear the end indicator. */
c89atomic_exchange_32(&pSound->atEnd, MA_FALSE);
c89atomic_exchange_8(&pSound->atEnd, MA_FALSE);
}
/* Make sure the sound is started. If there's a start delay, the sound won't actually start until the start time is reached. */
......@@ -10025,13 +10011,13 @@ MA_API ma_result ma_sound_set_rotation(ma_sound* pSound, ma_quat rotation)
return ma_spatializer_set_rotation(&pSound->engineNode.effect.spatializer, rotation);
}
MA_API ma_result ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping)
MA_API ma_result ma_sound_set_looping(ma_sound* pSound, ma_bool8 isLooping)
{
if (pSound == NULL) {
return MA_INVALID_ARGS;
}
c89atomic_exchange_32(&pSound->isLooping, isLooping);
c89atomic_exchange_8(&pSound->isLooping, isLooping);
/*
This is a little bit of a hack, but basically we need to set the looping flag at the data source level if we are running a data source managed by
......@@ -10085,31 +10071,22 @@ MA_API ma_result ma_sound_get_current_fade_volume(ma_sound* pSound, float* pVolu
return ma_fader_get_current_volume(&pSound->engineNode.effect.fader, pVolume);
}
MA_API ma_result ma_sound_set_start_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds)
MA_API ma_result ma_sound_set_start_delay(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames)
{
if (pSound == NULL) {
return MA_INVALID_ARGS;
}
/*
It's important that the delay be timed based on the engine's sample rate and not the rate of the sound. The reason is that no processing will be happening
by the sound before playback has actually begun and we won't have accurate frame counters due to resampling.
*/
pSound->startDelayInEngineFrames = (pSound->engineNode.pEngine->sampleRate * delayInMilliseconds) / 1000;
return MA_SUCCESS;
return ma_node_set_state_time(pSound, ma_node_state_started, absoluteGlobalTimeInFrames);
}
MA_API ma_result ma_sound_set_stop_delay(ma_sound* pSound, ma_uint64 delayInMilliseconds)
MA_API ma_result ma_sound_set_stop_delay(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames)
{
if (pSound == NULL) {
return MA_INVALID_ARGS;
}
pSound->stopDelayInEngineFrames = (pSound->engineNode.pEngine->sampleRate * delayInMilliseconds) / 1000;
return MA_SUCCESS;
return ma_node_set_state_time(pSound, ma_node_state_stopped, absoluteGlobalTimeInFrames);
}
MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound)
......@@ -10127,7 +10104,7 @@ MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound)
return MA_FALSE;
}
return c89atomic_load_32((ma_bool32*)&pSound->atEnd);
return c89atomic_load_8((ma_bool8*)&pSound->atEnd);
}
MA_API ma_result ma_sound_get_time_in_frames(const ma_sound* pSound, ma_uint64* pTimeInFrames)
......@@ -10342,26 +10319,22 @@ MA_API ma_result ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup,
return ma_fader_get_current_volume(&pGroup->engineNode.effect.fader, pVolume);
}
MA_API ma_result ma_sound_group_set_start_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds)
MA_API ma_result ma_sound_group_set_start_delay(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames)
{
if (pGroup == NULL) {
return MA_INVALID_ARGS;
}
pGroup->startDelayInEngineFrames = (pGroup->engineNode.pEngine->sampleRate * delayInMilliseconds) / 1000;
return MA_SUCCESS;
return ma_node_set_state_time(pGroup, ma_node_state_started, absoluteGlobalTimeInFrames);
}
MA_API ma_result ma_sound_group_set_stop_delay(ma_sound_group* pGroup, ma_uint64 delayInMilliseconds)
MA_API ma_result ma_sound_group_set_stop_delay(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames)
{
if (pGroup == NULL) {
return MA_INVALID_ARGS;
}
pGroup->stopDelayInEngineFrames = (pGroup->engineNode.pEngine->sampleRate * delayInMilliseconds) / 1000;
return MA_SUCCESS;
return ma_node_set_state_time(pGroup, ma_node_state_stopped, absoluteGlobalTimeInFrames);
}
MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup)
......
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