Commit 4060f18c authored by David Reid's avatar David Reid

Minor cleanup.

parent 9763fa62
......@@ -3246,7 +3246,7 @@ MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resourc
pageIndex0 = pDataStream->currentPageIndex;
pageIndex1 = (pDataStream->currentPageIndex + 1) & 0x01;
relativeCursor = pDataStream->relativeCursor;
relativeCursor = pDataStream->relativeCursor;
availableFrames = 0;
if (pDataStream->isPageValid[pageIndex0]) {
......@@ -4574,852 +4574,874 @@ MA_API ma_bool32 ma_fader_is_time_past_fade_end(const ma_fader* pFader)
Engine
**************************************************************************************************************************************************************/
static MA_INLINE ma_result ma_engine_sound_stop_internal(ma_engine* pEngine, ma_sound* pSound);
MA_API ma_engine_config ma_engine_config_init_default()
static ma_result ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
ma_engine_config config;
MA_ZERO_OBJECT(&config);
ma_uint64 frameCount;
ma_bool32 isFading = MA_FALSE;
config.format = ma_format_f32;
/*
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.
*/
frameCount = ma_min(*pFrameCountIn, *pFrameCountOut);
return config;
}
/* 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. */
if (pEngineEffect->fader.timeInFramesCur < pEngineEffect->fader.timeInFramesEnd) {
isFading = MA_TRUE; /* <-- Comment out this line to always run the fader. */
}
/* Panning. This is a no-op when the engine has only 1 channel or the pan is 0. */
if (pEngineEffect->pEngine->channels == 1 || pEngineEffect->panner.pan == 0) {
/* Fast path. No panning. */
if (pEngineEffect->isSpatial == MA_FALSE) {
/* Fast path. No spatialization. */
if (isFading == MA_FALSE) {
/* Fast path. No fading. */
if (pFramesIn == pFramesOut) {
/* Super fast path. No-op. */
} else {
/* Slow path. Copy. */
ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pEngineEffect->pEngine->format, pEngineEffect->pEngine->channels);
}
} else {
/* Slow path. Fading required. */
ma_fader_process_pcm_frames(&pEngineEffect->fader, pFramesOut, pFramesIn, frameCount);
}
} else {
/* Slow path. Spatialization required. */
ma_spatializer_process_pcm_frames(&pEngineEffect->spatializer, pFramesOut, pFramesIn, frameCount);
static void ma_engine_sound_mix_wait(ma_sound* pSound)
{
/* 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. */
}
}
} else {
/* Slow path. Panning required. */
ma_panner_process_pcm_frames(&pEngineEffect->panner, pFramesOut, pFramesIn, frameCount);
/* Just do a basic spin wait. */
while (pSound->isMixing) {
ma_yield();
}
}
if (pEngineEffect->isSpatial == MA_FALSE) {
/* 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. */
}
}
static void ma_engine_mix_sound(ma_engine* pEngine, ma_sound_group* pGroup, ma_sound* pSound, ma_uint32 frameCount)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pGroup != NULL);
MA_ASSERT(pSound != NULL);
*pFrameCountIn = frameCount;
*pFrameCountOut = frameCount;
c89atomic_exchange_32(&pSound->isMixing, MA_TRUE); /* This must be done before checking the isPlaying state. */
{
if (pSound->isPlaying) {
ma_result result = MA_SUCCESS;
return MA_SUCCESS;
}
/* If the pitch has changed we need to update the resampler. */
if (pSound->effect.oldPitch != pSound->effect.pitch) {
pSound->effect.oldPitch = pSound->effect.pitch;
ma_data_converter_set_rate_ratio(&pSound->effect.converter, pSound->effect.pitch);
}
static ma_result ma_engine_effect__on_process_pcm_frames__no_pre_effect(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
ma_bool32 isPitchingRequired = MA_TRUE;
/*
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
data conversion should be necessary here.
*/
if (pSound->volume > 0 || pSound->effect.pPreEffect != NULL || pSound->effect.pitch != 1) {
result = ma_mixer_mix_data_source(&pGroup->mixer, pSound->pDataSource, frameCount, pSound->volume, &pSound->effect, pSound->isLooping);
} else {
/* 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. */
result = ma_data_source_seek_pcm_frames(pSound->pDataSource, frameCount, NULL, pSound->isLooping);
}
/* Fast path for when no pitching is required. */
if (isPitchingRequired == MA_FALSE) {
/* Fast path. No pitch shifting. */
return ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
} else {
/* Slow path. Pitch shifting required. We need to run everything through our data converter first. */
/* If fading out we need to stop the sound if it's done fading. */
if (pSound->isFadingOut) {
if (ma_fader_is_time_past_fade_end(&pSound->effect.fader)) {
ma_engine_sound_stop_internal(pEngine, pSound);
}
}
/*
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_result result;
ma_uint64 frameCountIn;
ma_uint64 frameCountOut;
/* 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(). */
}
result = ma_data_converter_process_pcm_frames(&pEngineEffect->converter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
if (result != MA_SUCCESS) {
return result;
}
/* 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;
return ma_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. */
}
c89atomic_exchange_32(&pSound->isMixing, MA_FALSE);
}
static void ma_engine_mix_sound_group(ma_engine* pEngine, ma_sound_group* pGroup, void* pFramesOut, ma_uint32 frameCount)
static ma_result ma_engine_effect__on_process_pcm_frames__general(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
ma_result result;
ma_mixer* pParentMixer = NULL;
ma_uint64 frameCountOut;
ma_uint64 frameCountIn;
ma_sound_group* pNextChildGroup;
ma_sound* pNextSound;
ma_uint64 frameCountIn = *pFrameCountIn;
ma_uint64 frameCountOut = *pFrameCountOut;
ma_uint64 totalFramesProcessedIn = 0;
ma_uint64 totalFramesProcessedOut = 0;
ma_format effectFormat;
ma_uint32 effectChannels;
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pGroup != NULL);
MA_ASSERT(frameCount != 0);
MA_ASSERT(pEngineEffect != NULL);
MA_ASSERT(pEngineEffect->pPreEffect != NULL);
MA_ASSERT(pFramesIn != NULL);
MA_ASSERT(pFrameCountIn != NULL);
MA_ASSERT(pFramesOut != NULL);
MA_ASSERT(pFrameCountOut != NULL);
/* Don't do anything if we're not playing. */
if (pGroup->isPlaying == MA_FALSE) {
return;
}
/* 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.
*/
while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) {
ma_uint8 preEffectOutBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* effectFormat / effectChannels */
ma_uint32 preEffectOutBufferCap = sizeof(preEffectOutBuffer) / ma_get_bytes_per_frame(effectFormat, effectChannels);
const void* pRunningFramesIn = ma_offset_ptr(pFramesIn, totalFramesProcessedIn * ma_get_bytes_per_frame(effectFormat, effectChannels));
/* */ void* pRunningFramesOut = ma_offset_ptr(pFramesOut, totalFramesProcessedOut * ma_get_bytes_per_frame(effectFormat, effectChannels));
ma_uint64 frameCountInThisIteration;
ma_uint64 frameCountOutThisIteration;
if (pGroup->pParent != NULL) {
pParentMixer = &pGroup->pParent->mixer;
}
frameCountOutThisIteration = frameCountOut - totalFramesProcessedOut;
if (frameCountOutThisIteration > preEffectOutBufferCap) {
frameCountOutThisIteration = preEffectOutBufferCap;
}
frameCountOut = frameCount;
frameCountIn = frameCount;
/* We need to ensure we don't read too many input frames that we won't be able to process them all in the next step. */
frameCountInThisIteration = ma_data_converter_get_required_input_frame_count(&pEngineEffect->converter, frameCountOutThisIteration);
if (frameCountInThisIteration > (frameCountIn - totalFramesProcessedIn)) {
frameCountInThisIteration = (frameCountIn - totalFramesProcessedIn);
}
/* Before can mix the group we need to mix it's children. */
result = ma_mixer_begin(&pGroup->mixer, pParentMixer, &frameCountOut, &frameCountIn);
if (result != MA_SUCCESS) {
return;
}
result = ma_effect_process_pcm_frames_ex(pEngineEffect->pPreEffect, pRunningFramesIn, &frameCountInThisIteration, preEffectOutBuffer, &frameCountOutThisIteration, effectFormat, effectChannels, effectFormat, effectChannels);
if (result != MA_SUCCESS) {
break;
}
MA_ASSERT(frameCountIn < 0xFFFFFFFF);
totalFramesProcessedIn += frameCountInThisIteration;
/* Child groups need to be mixed based on the parent's input frame count. */
for (pNextChildGroup = pGroup->pFirstChild; pNextChildGroup != NULL; pNextChildGroup = pNextChildGroup->pNextSibling) {
ma_engine_mix_sound_group(pEngine, pNextChildGroup, NULL, (ma_uint32)frameCountIn); /* Safe cast. */
}
/* At this point we have run the pre-effect and we can now run it through the main engine effect. */
frameCountOutThisIteration = frameCountOut - totalFramesProcessedOut; /* Process as many frames as will fit in the output buffer. */
result = ma_engine_effect__on_process_pcm_frames__no_pre_effect(pEngineEffect, preEffectOutBuffer, &frameCountInThisIteration, pRunningFramesOut, &frameCountOutThisIteration);
if (result != MA_SUCCESS) {
break;
}
/* Sounds in the group can now be mixed. This is where the real mixing work is done. */
for (pNextSound = pGroup->pFirstSoundInGroup; pNextSound != NULL; pNextSound = pNextSound->pNextSoundInGroup) {
ma_engine_mix_sound(pEngine, pGroup, pNextSound, (ma_uint32)frameCountIn); /* Safe cast. */
totalFramesProcessedIn += frameCountOutThisIteration;
}
/* Now mix into the parent. */
result = ma_mixer_end(&pGroup->mixer, pParentMixer, pFramesOut);
if (result != MA_SUCCESS) {
return;
}
}
static void ma_engine_listener__data_callback_fixed(ma_engine* pEngine, void* pFramesOut, ma_uint32 frameCount)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEngine->periodSizeInFrames == frameCount); /* This must always be true. */
*pFrameCountIn = totalFramesProcessedIn;
*pFrameCountOut = totalFramesProcessedOut;
/* Recursively mix the sound groups. */
ma_engine_mix_sound_group(pEngine, &pEngine->masterSoundGroup, pFramesOut, frameCount);
return MA_SUCCESS;
}
static void ma_engine_listener__data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
static ma_result ma_engine_effect__on_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
ma_uint32 pcmFramesAvailableInRB;
ma_uint32 pcmFramesProcessed = 0;
ma_uint8* pRunningOutput = (ma_uint8*)pFramesOut;
ma_engine* pEngine = (ma_engine*)pDevice->pUserData;
MA_ASSERT(pEngine != NULL);
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
/* We need to do updates in fixed sizes based on the engine's period size in frames. */
MA_ASSERT(pEffect != NULL);
/*
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.
*/
while (pcmFramesProcessed < frameCount) { /* Keep going until we've filled the output buffer. */
ma_uint32 framesRemaining = frameCount - pcmFramesProcessed;
/* Optimized path for when there is no pre-effect. */
if (pEngineEffect->pPreEffect == NULL) {
return ma_engine_effect__on_process_pcm_frames__no_pre_effect(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
} else {
return ma_engine_effect__on_process_pcm_frames__general(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
}
}
pcmFramesAvailableInRB = ma_pcm_rb_available_read(&pEngine->listener.fixedRB);
if (pcmFramesAvailableInRB > 0) {
ma_uint32 framesToRead = (framesRemaining < pcmFramesAvailableInRB) ? framesRemaining : pcmFramesAvailableInRB;
void* pReadBuffer;
static ma_uint64 ma_engine_effect__on_get_required_input_frame_count(ma_effect* pEffect, ma_uint64 outputFrameCount)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
ma_uint64 inputFrameCount;
ma_pcm_rb_acquire_read(&pEngine->listener.fixedRB, &framesToRead, &pReadBuffer);
{
memcpy(pRunningOutput, pReadBuffer, framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
}
ma_pcm_rb_commit_read(&pEngine->listener.fixedRB, framesToRead, pReadBuffer);
MA_ASSERT(pEffect != NULL);
pRunningOutput += framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels);
pcmFramesProcessed += framesToRead;
} else {
/*
There's nothing in the buffer. Fill it with more data from the callback. We reset the buffer first so that the read and write pointers
are reset back to the start so we can fill the ring buffer in chunks of pEngine->periodSizeInFrames which is what we initialized it
with. Note that this is not how you would want to do it in a multi-threaded environment. In this case you would want to seek the write
pointer forward via the producer thread and the read pointer forward via the consumer thread (this thread).
*/
ma_uint32 framesToWrite = pEngine->periodSizeInFrames;
void* pWriteBuffer;
inputFrameCount = ma_data_converter_get_required_input_frame_count(&pEngineEffect->converter, outputFrameCount);
ma_pcm_rb_reset(&pEngine->listener.fixedRB);
ma_pcm_rb_acquire_write(&pEngine->listener.fixedRB, &framesToWrite, &pWriteBuffer);
{
MA_ASSERT(framesToWrite == pEngine->periodSizeInFrames); /* <-- This should always work in this example because we just reset the ring buffer. */
ma_engine_listener__data_callback_fixed(pEngine, pWriteBuffer, framesToWrite);
}
ma_pcm_rb_commit_write(&pEngine->listener.fixedRB, framesToWrite, pWriteBuffer);
if (pEngineEffect->pPreEffect != NULL) {
ma_uint64 preEffectInputFrameCount = ma_effect_get_required_input_frame_count(pEngineEffect->pPreEffect, outputFrameCount);
if (inputFrameCount < preEffectInputFrameCount) {
inputFrameCount = preEffectInputFrameCount;
}
}
(void)pFramesIn;
return inputFrameCount;
}
static ma_result ma_engine_listener_init(ma_engine* pEngine, const ma_device_id* pPlaybackDeviceID, ma_listener* pListener)
static ma_uint64 ma_engine_effect__on_get_expected_output_frame_count(ma_effect* pEffect, ma_uint64 inputFrameCount)
{
ma_result result;
ma_device_config deviceConfig;
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
ma_uint64 outputFrameCount;
if (pListener == NULL) {
return MA_INVALID_ARGS;
}
MA_ASSERT(pEffect != NULL);
MA_ZERO_OBJECT(pListener);
outputFrameCount = ma_data_converter_get_expected_output_frame_count(&pEngineEffect->converter, inputFrameCount);
if (pEngine == NULL) {
return MA_INVALID_ARGS;
if (pEngineEffect->pPreEffect != NULL) {
ma_uint64 preEffectOutputFrameCount = ma_effect_get_expected_output_frame_count(pEngineEffect->pPreEffect, inputFrameCount);
if (outputFrameCount > preEffectOutputFrameCount) {
outputFrameCount = preEffectOutputFrameCount;
}
}
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.pDeviceID = pPlaybackDeviceID;
deviceConfig.playback.format = pEngine->format;
deviceConfig.playback.channels = pEngine->channels;
deviceConfig.sampleRate = pEngine->sampleRate;
deviceConfig.dataCallback = ma_engine_listener__data_callback;
deviceConfig.pUserData = pEngine;
deviceConfig.periodSizeInFrames = pEngine->periodSizeInFrames;
deviceConfig.periodSizeInMilliseconds = pEngine->periodSizeInMilliseconds;
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. */
return outputFrameCount;
}
result = ma_device_init(&pEngine->context, &deviceConfig, &pListener->device);
if (result != MA_SUCCESS) {
return result;
}
static ma_result ma_engine_effect__on_get_input_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
/* 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. */
result = ma_pcm_rb_init(pListener->device.playback.format, pListener->device.playback.channels, pListener->device.playback.internalPeriodSizeInFrames, NULL, &pEngine->allocationCallbacks, &pListener->fixedRB);
if (result != MA_SUCCESS) {
return result;
}
MA_ASSERT(pEffect != NULL);
return MA_SUCCESS;
if (pEngineEffect->pPreEffect != NULL) {
return ma_engine_effect__on_get_input_data_format(pEffect, pFormat, pChannels, pSampleRate);
} else {
*pFormat = pEngineEffect->converter.config.formatIn;
*pChannels = pEngineEffect->converter.config.channelsIn;
*pSampleRate = pEngineEffect->converter.config.sampleRateIn;
return MA_SUCCESS;
}
}
static void ma_engine_listener_uninit(ma_engine* pEngine, ma_listener* pListener)
static ma_result ma_engine_effect__on_get_output_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
{
if (pEngine == NULL || pListener == NULL) {
return;
}
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
ma_device_uninit(&pListener->device);
MA_ASSERT(pEffect != NULL);
*pFormat = pEngineEffect->converter.config.formatOut;
*pChannels = pEngineEffect->converter.config.channelsOut;
*pSampleRate = pEngineEffect->converter.config.sampleRateOut;
return MA_SUCCESS;
}
MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine)
static ma_result ma_engine_effect_init(ma_engine* pEngine, ma_engine_effect* pEffect)
{
ma_result result;
ma_engine_config engineConfig;
ma_context_config contextConfig;
ma_panner_config pannerConfig;
ma_spatializer_config spatializerConfig;
ma_fader_config faderConfig;
ma_data_converter_config converterConfig;
/* 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).
*/
if (engineConfig.format != ma_format_f32) {
return MA_INVALID_ARGS; /* Format not supported. */
}
MA_ZERO_OBJECT(pEffect);
pEngine->pResourceManager = engineConfig.pResourceManager;
pEngine->format = engineConfig.format;
pEngine->channels = engineConfig.channels;
pEngine->sampleRate = engineConfig.sampleRate;
pEngine->periodSizeInFrames = engineConfig.periodSizeInFrames;
pEngine->periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds;
ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks);
pEffect->baseEffect.onProcessPCMFrames = ma_engine_effect__on_process_pcm_frames;
pEffect->baseEffect.onGetRequiredInputFrameCount = ma_engine_effect__on_get_required_input_frame_count;
pEffect->baseEffect.onGetExpectedOutputFrameCount = ma_engine_effect__on_get_expected_output_frame_count;
pEffect->baseEffect.onGetInputDataFormat = ma_engine_effect__on_get_input_data_format;
pEffect->baseEffect.onGetOutputDataFormat = ma_engine_effect__on_get_output_data_format;
pEffect->pEngine = pEngine;
pEffect->pPreEffect = NULL;
pEffect->pitch = 1;
pEffect->oldPitch = 1;
/* We need a context before we'll be able to create the default listener. */
contextConfig = ma_context_config_init();
contextConfig.allocationCallbacks = pEngine->allocationCallbacks;
pannerConfig = ma_panner_config_init(pEngine->format, pEngine->channels);
result = ma_panner_init(&pannerConfig, &pEffect->panner);
if (result != MA_SUCCESS) {
return result; /* Failed to create the panner. */
}
result = ma_context_init(NULL, 0, &contextConfig, &pEngine->context);
spatializerConfig = ma_spatializer_config_init(pEngine, pEngine->format, pEngine->channels);
result = ma_spatializer_init(&spatializerConfig, &pEffect->spatializer);
if (result != MA_SUCCESS) {
return result; /* Failed to initialize context. */
return result; /* 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. */
result = ma_engine_listener_init(pEngine, engineConfig.pPlaybackDeviceID, &pEngine->listener);
faderConfig = ma_fader_config_init(pEngine->format, pEngine->channels, pEngine->sampleRate);
result = ma_fader_init(&faderConfig, &pEffect->fader);
if (result != MA_SUCCESS) {
ma_context_uninit(&pEngine->context);
return result; /* Failed to initialize default listener. */
return result; /* 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. */
pEngine->format = pEngine->listener.device.playback.format;
pEngine->channels = pEngine->listener.device.playback.channels;
pEngine->sampleRate = pEngine->listener.device.sampleRate;
pEngine->periodSizeInFrames = pEngine->listener.device.playback.internalPeriodSizeInFrames;
pEngine->periodSizeInMilliseconds = (pEngine->periodSizeInFrames * pEngine->sampleRate) / 1000;
/* Our effect processor requires f32 for now, but I may implement an s16 optimized pipeline. */
/* We need a default sound group. This must be done after setting the format, channels and sample rate to their proper values. */
result = ma_engine_sound_group_init(pEngine, NULL, &pEngine->masterSoundGroup);
converterConfig = ma_data_converter_config_init(pEngine->format, pEngine->format, pEngine->channels, pEngine->channels, pEngine->sampleRate, pEngine->sampleRate);
/*
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. */
converterConfig.resampling.algorithm = ma_resample_algorithm_linear;
converterConfig.resampling.linear.lpfOrder = 0;
result = ma_data_converter_init(&converterConfig, &pEffect->converter);
if (result != MA_SUCCESS) {
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
return result; /* Failed to initialize master sound group. */
return result;
}
return MA_SUCCESS;
}
static void ma_engine_effect_uninit(ma_engine* pEngine, ma_engine_effect* pEffect)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEffect != NULL);
(void)pEngine;
ma_data_converter_uninit(&pEffect->converter);
}
static ma_result ma_engine_effect_reinit(ma_engine* pEngine, ma_engine_effect* pEffect)
{
/* This function assumes the data converter was previously initialized and needs to be uninitialized. */
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEffect != NULL);
/* We need a resource manager. */
#ifndef MA_NO_RESOURCE_MANAGER
if (pEngine->pResourceManager == NULL) {
ma_resource_manager_config resourceManagerConfig;
ma_engine_effect_uninit(pEngine, pEffect);
pEngine->pResourceManager = (ma_resource_manager*)ma__malloc_from_callbacks(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks);
if (pEngine->pResourceManager == NULL) {
ma_engine_sound_group_uninit(pEngine, &pEngine->masterSoundGroup);
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
return MA_OUT_OF_MEMORY;
}
return ma_engine_effect_init(pEngine, pEffect);
}
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = pEngine->format;
resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */
resourceManagerConfig.decodedSampleRate = pEngine->sampleRate;
ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks);
static ma_bool32 ma_engine_effect_is_passthrough(ma_engine_effect* pEffect)
{
MA_ASSERT(pEffect != NULL);
result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager);
if (result != MA_SUCCESS) {
ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks);
ma_engine_sound_group_uninit(pEngine, &pEngine->masterSoundGroup);
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
return result;
}
/* A pre-effect will require processing. */
if (pEffect->pPreEffect != NULL) {
return MA_FALSE;
}
pEngine->ownsResourceManager = MA_TRUE;
/* If pitch shifting we'll need to do processing through the resampler. */
if (pEffect->pitch != 1) {
return MA_FALSE;
}
#endif
/* Start the engine if required. This should always be the last step. */
if (engineConfig.noAutoStart == MA_FALSE) {
result = ma_engine_start(pEngine);
if (result != MA_SUCCESS) {
ma_engine_uninit(pEngine);
return result; /* Failed to start the engine. */
}
/* If we're fading we need to make sure we do processing. */
if (ma_fader_is_time_past_fade_end(&pEffect->fader) == MA_FALSE) {
return MA_FALSE;
}
return MA_SUCCESS;
return MA_TRUE;
}
MA_API void ma_engine_uninit(ma_engine* pEngine)
static MA_INLINE ma_result ma_engine_sound_stop_internal(ma_engine* pEngine, ma_sound* pSound);
MA_API ma_engine_config ma_engine_config_init_default()
{
if (pEngine == NULL) {
return;
}
ma_engine_config config;
MA_ZERO_OBJECT(&config);
ma_engine_sound_group_uninit(pEngine, &pEngine->masterSoundGroup);
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
config.format = ma_format_f32;
/* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */
#ifndef MA_NO_RESOURCE_MANAGER
if (pEngine->ownsResourceManager) {
ma_resource_manager_uninit(pEngine->pResourceManager);
ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks);
}
#endif
return config;
}
MA_API ma_result ma_engine_start(ma_engine* pEngine)
static void ma_engine_sound_mix_wait(ma_sound* pSound)
{
ma_result result;
/* This function is only safe when the sound is not flagged as playing. */
MA_ASSERT(pSound->isPlaying == MA_FALSE);
if (pEngine == NULL) {
return MA_INVALID_ARGS;
/* Just do a basic spin wait. */
while (pSound->isMixing) {
ma_yield();
}
}
result = ma_device_start(&pEngine->listener.device);
if (result != MA_SUCCESS) {
return result;
}
return MA_SUCCESS;
static void ma_engine_mix_sound(ma_engine* pEngine, ma_sound_group* pGroup, ma_sound* pSound, ma_uint32 frameCount)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pGroup != NULL);
MA_ASSERT(pSound != NULL);
c89atomic_exchange_32(&pSound->isMixing, MA_TRUE); /* This must be done before checking the isPlaying state. */
{
if (pSound->isPlaying) {
ma_result result = MA_SUCCESS;
/* If the pitch has changed we need to update the resampler. */
if (pSound->effect.oldPitch != pSound->effect.pitch) {
pSound->effect.oldPitch = pSound->effect.pitch;
ma_data_converter_set_rate_ratio(&pSound->effect.converter, pSound->effect.pitch);
}
/*
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.
*/
if (pSound->volume > 0 || ma_engine_effect_is_passthrough(&pSound->effect) == MA_FALSE) {
result = ma_mixer_mix_data_source(&pGroup->mixer, pSound->pDataSource, frameCount, pSound->volume, &pSound->effect, pSound->isLooping);
} else {
/* 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. */
result = ma_data_source_seek_pcm_frames(pSound->pDataSource, frameCount, NULL, pSound->isLooping);
}
/* If fading out we need to stop the sound if it's done fading. */
if (pSound->isFadingOut) {
if (ma_fader_is_time_past_fade_end(&pSound->effect.fader)) {
ma_engine_sound_stop_internal(pEngine, pSound);
}
}
/* 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(). */
}
}
}
c89atomic_exchange_32(&pSound->isMixing, MA_FALSE);
}
MA_API ma_result ma_engine_stop(ma_engine* pEngine)
static void ma_engine_mix_sound_group(ma_engine* pEngine, ma_sound_group* pGroup, void* pFramesOut, ma_uint32 frameCount)
{
ma_result result;
ma_mixer* pParentMixer = NULL;
ma_uint64 frameCountOut;
ma_uint64 frameCountIn;
ma_sound_group* pNextChildGroup;
ma_sound* pNextSound;
if (pEngine == NULL) {
return MA_INVALID_ARGS;
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pGroup != NULL);
MA_ASSERT(frameCount != 0);
/* Don't do anything if we're not playing. */
if (pGroup->isPlaying == MA_FALSE) {
return;
}
result = ma_device_stop(&pEngine->listener.device);
if (pGroup->pParent != NULL) {
pParentMixer = &pGroup->pParent->mixer;
}
frameCountOut = frameCount;
frameCountIn = frameCount;
/* Before can mix the group we need to mix it's children. */
result = ma_mixer_begin(&pGroup->mixer, pParentMixer, &frameCountOut, &frameCountIn);
if (result != MA_SUCCESS) {
return result;
return;
}
return MA_SUCCESS;
}
MA_ASSERT(frameCountIn < 0xFFFFFFFF);
MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume)
{
if (pEngine == NULL) {
return MA_INVALID_ARGS;
/* Child groups need to be mixed based on the parent's input frame count. */
for (pNextChildGroup = pGroup->pFirstChild; pNextChildGroup != NULL; pNextChildGroup = pNextChildGroup->pNextSibling) {
ma_engine_mix_sound_group(pEngine, pNextChildGroup, NULL, (ma_uint32)frameCountIn); /* Safe cast. */
}
return ma_device_set_master_volume(&pEngine->listener.device, volume);
/* Sounds in the group can now be mixed. This is where the real mixing work is done. */
for (pNextSound = pGroup->pFirstSoundInGroup; pNextSound != NULL; pNextSound = pNextSound->pNextSoundInGroup) {
ma_engine_mix_sound(pEngine, pGroup, pNextSound, (ma_uint32)frameCountIn); /* Safe cast. */
}
/* Now mix into the parent. */
result = ma_mixer_end(&pGroup->mixer, pParentMixer, pFramesOut);
if (result != MA_SUCCESS) {
return;
}
}
MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB)
static void ma_engine_listener__data_callback_fixed(ma_engine* pEngine, void* pFramesOut, ma_uint32 frameCount)
{
if (pEngine == NULL) {
return MA_INVALID_ARGS;
}
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEngine->periodSizeInFrames == frameCount); /* This must always be true. */
return ma_device_set_master_gain_db(&pEngine->listener.device, gainDB);
/* Recursively mix the sound groups. */
ma_engine_mix_sound_group(pEngine, &pEngine->masterSoundGroup, pFramesOut, frameCount);
}
static ma_result ma_engine_sound_detach(ma_engine* pEngine, ma_sound* pSound)
static void ma_engine_listener__data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{
ma_sound_group* pGroup;
ma_uint32 pcmFramesAvailableInRB;
ma_uint32 pcmFramesProcessed = 0;
ma_uint8* pRunningOutput = (ma_uint8*)pFramesOut;
ma_engine* pEngine = (ma_engine*)pDevice->pUserData;
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pSound != NULL);
pGroup = pSound->pGroup;
MA_ASSERT(pGroup != NULL);
/* 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. */
ma_uint32 framesRemaining = frameCount - pcmFramesProcessed;
/*
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).
pcmFramesAvailableInRB = ma_pcm_rb_available_read(&pEngine->listener.fixedRB);
if (pcmFramesAvailableInRB > 0) {
ma_uint32 framesToRead = (framesRemaining < pcmFramesAvailableInRB) ? framesRemaining : pcmFramesAvailableInRB;
void* pReadBuffer;
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. */
ma_pcm_rb_acquire_read(&pEngine->listener.fixedRB, &framesToRead, &pReadBuffer);
{
memcpy(pRunningOutput, pReadBuffer, framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels));
}
ma_pcm_rb_commit_read(&pEngine->listener.fixedRB, framesToRead, pReadBuffer);
/* Make a new head. */
c89atomic_exchange_ptr(&pGroup->pFirstSoundInGroup, pSound->pNextSoundInGroup);
pRunningOutput += framesToRead * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels);
pcmFramesProcessed += framesToRead;
} else {
/*
The sound is not the head. We need to remove the sound from the group by simply changing the pNextSoundInGroup member of the previous sound. This is
the important part. This is the part that allows the mixing thread to continue iteration without locking.
There's nothing in the buffer. Fill it with more data from the callback. We reset the buffer first so that the read and write pointers
are reset back to the start so we can fill the ring buffer in chunks of pEngine->periodSizeInFrames which is what we initialized it
with. Note that this is not how you would want to do it in a multi-threaded environment. In this case you would want to seek the write
pointer forward via the producer thread and the read pointer forward via the consumer thread (this thread).
*/
c89atomic_exchange_ptr(&pSound->pPrevSoundInGroup->pNextSoundInGroup, pSound->pNextSoundInGroup);
}
ma_uint32 framesToWrite = pEngine->periodSizeInFrames;
void* pWriteBuffer;
/* This doesn't really need to be done atomically because we've wrapped this in a lock and it's not used by the mixing thread. */
if (pSound->pNextSoundInGroup != NULL) {
c89atomic_exchange_ptr(&pSound->pNextSoundInGroup->pPrevSoundInGroup, pSound->pPrevSoundInGroup);
ma_pcm_rb_reset(&pEngine->listener.fixedRB);
ma_pcm_rb_acquire_write(&pEngine->listener.fixedRB, &framesToWrite, &pWriteBuffer);
{
MA_ASSERT(framesToWrite == pEngine->periodSizeInFrames); /* <-- This should always work in this example because we just reset the ring buffer. */
ma_engine_listener__data_callback_fixed(pEngine, pWriteBuffer, framesToWrite);
}
ma_pcm_rb_commit_write(&pEngine->listener.fixedRB, framesToWrite, pWriteBuffer);
}
}
ma_mutex_unlock(&pGroup->lock);
return MA_SUCCESS;
(void)pFramesIn;
}
static ma_result ma_engine_sound_attach(ma_engine* pEngine, ma_sound* pSound, ma_sound_group* pGroup)
static ma_result ma_engine_listener_init(ma_engine* pEngine, const ma_device_id* pPlaybackDeviceID, ma_listener* pListener)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pSound != NULL);
MA_ASSERT(pGroup != NULL);
MA_ASSERT(pSound->pGroup == NULL);
ma_result result;
ma_device_config deviceConfig;
/* 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);
if (pListener == NULL) {
return MA_INVALID_ARGS;
}
/* We can set the group at the start. */
pSound->pGroup = pGroup;
MA_ZERO_OBJECT(pListener);
/*
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.
if (pEngine == NULL) {
return MA_INVALID_ARGS;
}
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.
*/
ma_mutex_lock(&pGroup->lock);
{
ma_sound* pNewFirstSoundInGroup = pSound;
ma_sound* pOldFirstSoundInGroup = pGroup->pFirstSoundInGroup;
deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.pDeviceID = pPlaybackDeviceID;
deviceConfig.playback.format = pEngine->format;
deviceConfig.playback.channels = pEngine->channels;
deviceConfig.sampleRate = pEngine->sampleRate;
deviceConfig.dataCallback = ma_engine_listener__data_callback;
deviceConfig.pUserData = pEngine;
deviceConfig.periodSizeInFrames = pEngine->periodSizeInFrames;
deviceConfig.periodSizeInMilliseconds = pEngine->periodSizeInMilliseconds;
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. */
pNewFirstSoundInGroup->pNextSoundInGroup = pOldFirstSoundInGroup;
if (pOldFirstSoundInGroup != NULL) {
pOldFirstSoundInGroup->pPrevSoundInGroup = pNewFirstSoundInGroup;
}
result = ma_device_init(&pEngine->context, &deviceConfig, &pListener->device);
if (result != MA_SUCCESS) {
return result;
}
c89atomic_exchange_ptr(&pGroup->pFirstSoundInGroup, pNewFirstSoundInGroup);
/* 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. */
result = ma_pcm_rb_init(pListener->device.playback.format, pListener->device.playback.channels, pListener->device.playback.internalPeriodSizeInFrames, NULL, &pEngine->allocationCallbacks, &pListener->fixedRB);
if (result != MA_SUCCESS) {
return result;
}
ma_mutex_unlock(&pGroup->lock);
return MA_SUCCESS;
}
static ma_result ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
static void ma_engine_listener_uninit(ma_engine* pEngine, ma_listener* pListener)
{
ma_uint64 frameCount;
ma_bool32 isFading = MA_FALSE;
/*
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.
*/
frameCount = ma_min(*pFrameCountIn, *pFrameCountOut);
/* 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. */
if (pEngineEffect->fader.timeInFramesCur < pEngineEffect->fader.timeInFramesEnd) {
isFading = MA_TRUE; /* <-- Comment out this line to always run the fader. */
if (pEngine == NULL || pListener == NULL) {
return;
}
/* Panning. This is a no-op when the engine has only 1 channel or the pan is 0. */
if (pEngineEffect->pEngine->channels == 1 || pEngineEffect->panner.pan == 0) {
/* Fast path. No panning. */
if (pEngineEffect->isSpatial == MA_FALSE) {
/* Fast path. No spatialization. */
if (isFading == MA_FALSE) {
/* Fast path. No fading. */
if (pFramesIn == pFramesOut) {
/* Super fast path. No-op. */
} else {
/* Slow path. Copy. */
ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pEngineEffect->pEngine->format, pEngineEffect->pEngine->channels);
}
} else {
/* Slow path. Fading required. */
ma_fader_process_pcm_frames(&pEngineEffect->fader, pFramesOut, pFramesIn, frameCount);
}
} else {
/* Slow path. Spatialization required. */
ma_spatializer_process_pcm_frames(&pEngineEffect->spatializer, pFramesOut, pFramesIn, frameCount);
ma_device_uninit(&pListener->device);
}
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. */
}
}
} else {
/* Slow path. Panning required. */
ma_panner_process_pcm_frames(&pEngineEffect->panner, pFramesOut, pFramesIn, frameCount);
MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine)
{
ma_result result;
ma_engine_config engineConfig;
ma_context_config contextConfig;
if (pEngineEffect->isSpatial == MA_FALSE) {
/* 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).
*/
if (engineConfig.format != ma_format_f32) {
return MA_INVALID_ARGS; /* Format not supported. */
}
*pFrameCountIn = frameCount;
*pFrameCountOut = frameCount;
pEngine->pResourceManager = engineConfig.pResourceManager;
pEngine->format = engineConfig.format;
pEngine->channels = engineConfig.channels;
pEngine->sampleRate = engineConfig.sampleRate;
pEngine->periodSizeInFrames = engineConfig.periodSizeInFrames;
pEngine->periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds;
ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks);
return MA_SUCCESS;
}
static ma_result ma_engine_effect__on_process_pcm_frames__no_pre_effect(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
ma_bool32 isPitchingRequired = MA_TRUE;
/* We need a context before we'll be able to create the default listener. */
contextConfig = ma_context_config_init();
contextConfig.allocationCallbacks = pEngine->allocationCallbacks;
/*
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
data conversion should be necessary here.
*/
result = ma_context_init(NULL, 0, &contextConfig, &pEngine->context);
if (result != MA_SUCCESS) {
return result; /* Failed to initialize context. */
}
/* Fast path for when no pitching is required. */
if (isPitchingRequired == MA_FALSE) {
/* Fast path. No pitch shifting. */
return ma_engine_effect__on_process_pcm_frames__no_pre_effect_no_pitch(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
} else {
/* 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. */
result = ma_engine_listener_init(pEngine, engineConfig.pPlaybackDeviceID, &pEngine->listener);
if (result != MA_SUCCESS) {
ma_context_uninit(&pEngine->context);
return result; /* 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_result result;
ma_uint64 frameCountIn;
ma_uint64 frameCountOut;
/* 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. */
pEngine->format = pEngine->listener.device.playback.format;
pEngine->channels = pEngine->listener.device.playback.channels;
pEngine->sampleRate = pEngine->listener.device.sampleRate;
pEngine->periodSizeInFrames = pEngine->listener.device.playback.internalPeriodSizeInFrames;
pEngine->periodSizeInMilliseconds = (pEngine->periodSizeInFrames * pEngine->sampleRate) / 1000;
result = ma_data_converter_process_pcm_frames(&pEngineEffect->converter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
if (result != MA_SUCCESS) {
return result;
}
/* 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;
return ma_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. */
result = ma_engine_sound_group_init(pEngine, NULL, &pEngine->masterSoundGroup);
if (result != MA_SUCCESS) {
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
return result; /* Failed to initialize master sound group. */
}
}
static ma_result ma_engine_effect__on_process_pcm_frames__general(ma_engine_effect* pEngineEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
{
ma_result result;
ma_uint64 frameCountIn = *pFrameCountIn;
ma_uint64 frameCountOut = *pFrameCountOut;
ma_uint64 totalFramesProcessedIn = 0;
ma_uint64 totalFramesProcessedOut = 0;
ma_format effectFormat;
ma_uint32 effectChannels;
MA_ASSERT(pEngineEffect != NULL);
MA_ASSERT(pEngineEffect->pPreEffect != NULL);
MA_ASSERT(pFramesIn != NULL);
MA_ASSERT(pFrameCountIn != NULL);
MA_ASSERT(pFramesOut != NULL);
MA_ASSERT(pFrameCountOut != NULL);
/* 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.
*/
while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) {
ma_uint8 preEffectOutBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* effectFormat / effectChannels */
ma_uint32 preEffectOutBufferCap = sizeof(preEffectOutBuffer) / ma_get_bytes_per_frame(effectFormat, effectChannels);
const void* pRunningFramesIn = ma_offset_ptr(pFramesIn, totalFramesProcessedIn * ma_get_bytes_per_frame(effectFormat, effectChannels));
/* */ void* pRunningFramesOut = ma_offset_ptr(pFramesOut, totalFramesProcessedOut * ma_get_bytes_per_frame(effectFormat, effectChannels));
ma_uint64 frameCountInThisIteration;
ma_uint64 frameCountOutThisIteration;
/* We need a resource manager. */
#ifndef MA_NO_RESOURCE_MANAGER
if (pEngine->pResourceManager == NULL) {
ma_resource_manager_config resourceManagerConfig;
frameCountOutThisIteration = frameCountOut - totalFramesProcessedOut;
if (frameCountOutThisIteration > preEffectOutBufferCap) {
frameCountOutThisIteration = preEffectOutBufferCap;
pEngine->pResourceManager = (ma_resource_manager*)ma__malloc_from_callbacks(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks);
if (pEngine->pResourceManager == NULL) {
ma_engine_sound_group_uninit(pEngine, &pEngine->masterSoundGroup);
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
return MA_OUT_OF_MEMORY;
}
/* We need to ensure we don't read too many input frames that we won't be able to process them all in the next step. */
frameCountInThisIteration = ma_data_converter_get_required_input_frame_count(&pEngineEffect->converter, frameCountOutThisIteration);
if (frameCountInThisIteration > (frameCountIn - totalFramesProcessedIn)) {
frameCountInThisIteration = (frameCountIn - totalFramesProcessedIn);
}
resourceManagerConfig = ma_resource_manager_config_init();
resourceManagerConfig.decodedFormat = pEngine->format;
resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */
resourceManagerConfig.decodedSampleRate = pEngine->sampleRate;
ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks);
result = ma_effect_process_pcm_frames_ex(pEngineEffect->pPreEffect, pRunningFramesIn, &frameCountInThisIteration, preEffectOutBuffer, &frameCountOutThisIteration, effectFormat, effectChannels, effectFormat, effectChannels);
result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager);
if (result != MA_SUCCESS) {
break;
ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks);
ma_engine_sound_group_uninit(pEngine, &pEngine->masterSoundGroup);
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
return result;
}
totalFramesProcessedIn += frameCountInThisIteration;
pEngine->ownsResourceManager = MA_TRUE;
}
#endif
/* At this point we have run the pre-effect and we can now run it through the main engine effect. */
frameCountOutThisIteration = frameCountOut - totalFramesProcessedOut; /* Process as many frames as will fit in the output buffer. */
result = ma_engine_effect__on_process_pcm_frames__no_pre_effect(pEngineEffect, preEffectOutBuffer, &frameCountInThisIteration, pRunningFramesOut, &frameCountOutThisIteration);
/* Start the engine if required. This should always be the last step. */
if (engineConfig.noAutoStart == MA_FALSE) {
result = ma_engine_start(pEngine);
if (result != MA_SUCCESS) {
break;
ma_engine_uninit(pEngine);
return result; /* Failed to start the engine. */
}
totalFramesProcessedIn += frameCountOutThisIteration;
}
*pFrameCountIn = totalFramesProcessedIn;
*pFrameCountOut = totalFramesProcessedOut;
return MA_SUCCESS;
}
static ma_result ma_engine_effect__on_process_pcm_frames(ma_effect* pEffect, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut)
MA_API void ma_engine_uninit(ma_engine* pEngine)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
if (pEngine == NULL) {
return;
}
MA_ASSERT(pEffect != NULL);
ma_engine_sound_group_uninit(pEngine, &pEngine->masterSoundGroup);
ma_engine_listener_uninit(pEngine, &pEngine->listener);
ma_context_uninit(&pEngine->context);
/* Optimized path for when there is no pre-effect. */
if (pEngineEffect->pPreEffect == NULL) {
return ma_engine_effect__on_process_pcm_frames__no_pre_effect(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
} else {
return ma_engine_effect__on_process_pcm_frames__general(pEngineEffect, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut);
/* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */
#ifndef MA_NO_RESOURCE_MANAGER
if (pEngine->ownsResourceManager) {
ma_resource_manager_uninit(pEngine->pResourceManager);
ma__free_from_callbacks(pEngine->pResourceManager, &pEngine->allocationCallbacks);
}
#endif
}
static ma_uint64 ma_engine_effect__on_get_required_input_frame_count(ma_effect* pEffect, ma_uint64 outputFrameCount)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
ma_uint64 inputFrameCount;
MA_ASSERT(pEffect != NULL);
MA_API ma_result ma_engine_start(ma_engine* pEngine)
{
ma_result result;
inputFrameCount = ma_data_converter_get_required_input_frame_count(&pEngineEffect->converter, outputFrameCount);
if (pEngine == NULL) {
return MA_INVALID_ARGS;
}
if (pEngineEffect->pPreEffect != NULL) {
ma_uint64 preEffectInputFrameCount = ma_effect_get_required_input_frame_count(pEngineEffect->pPreEffect, outputFrameCount);
if (inputFrameCount < preEffectInputFrameCount) {
inputFrameCount = preEffectInputFrameCount;
}
result = ma_device_start(&pEngine->listener.device);
if (result != MA_SUCCESS) {
return result;
}
return inputFrameCount;
return MA_SUCCESS;
}
static ma_uint64 ma_engine_effect__on_get_expected_output_frame_count(ma_effect* pEffect, ma_uint64 inputFrameCount)
MA_API ma_result ma_engine_stop(ma_engine* pEngine)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
ma_uint64 outputFrameCount;
MA_ASSERT(pEffect != NULL);
ma_result result;
outputFrameCount = ma_data_converter_get_expected_output_frame_count(&pEngineEffect->converter, inputFrameCount);
if (pEngine == NULL) {
return MA_INVALID_ARGS;
}
if (pEngineEffect->pPreEffect != NULL) {
ma_uint64 preEffectOutputFrameCount = ma_effect_get_expected_output_frame_count(pEngineEffect->pPreEffect, inputFrameCount);
if (outputFrameCount > preEffectOutputFrameCount) {
outputFrameCount = preEffectOutputFrameCount;
}
result = ma_device_stop(&pEngine->listener.device);
if (result != MA_SUCCESS) {
return result;
}
return outputFrameCount;
return MA_SUCCESS;
}
static ma_result ma_engine_effect__on_get_input_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
MA_ASSERT(pEffect != NULL);
if (pEngineEffect->pPreEffect != NULL) {
return ma_engine_effect__on_get_input_data_format(pEffect, pFormat, pChannels, pSampleRate);
} else {
*pFormat = pEngineEffect->converter.config.formatIn;
*pChannels = pEngineEffect->converter.config.channelsIn;
*pSampleRate = pEngineEffect->converter.config.sampleRateIn;
return MA_SUCCESS;
if (pEngine == NULL) {
return MA_INVALID_ARGS;
}
return ma_device_set_master_volume(&pEngine->listener.device, volume);
}
static ma_result ma_engine_effect__on_get_output_data_format(ma_effect* pEffect, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate)
MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB)
{
ma_engine_effect* pEngineEffect = (ma_engine_effect*)pEffect;
MA_ASSERT(pEffect != NULL);
*pFormat = pEngineEffect->converter.config.formatOut;
*pChannels = pEngineEffect->converter.config.channelsOut;
*pSampleRate = pEngineEffect->converter.config.sampleRateOut;
if (pEngine == NULL) {
return MA_INVALID_ARGS;
}
return MA_SUCCESS;
return ma_device_set_master_gain_db(&pEngine->listener.device, gainDB);
}
static ma_result ma_engine_effect_init(ma_engine* pEngine, ma_engine_effect* pEffect)
static ma_result ma_engine_sound_detach(ma_engine* pEngine, ma_sound* pSound)
{
ma_result result;
ma_panner_config pannerConfig;
ma_spatializer_config spatializerConfig;
ma_fader_config faderConfig;
ma_data_converter_config converterConfig;
ma_sound_group* pGroup;
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEffect != NULL);
MA_ASSERT(pSound != NULL);
MA_ZERO_OBJECT(pEffect);
pGroup = pSound->pGroup;
MA_ASSERT(pGroup != NULL);
pEffect->baseEffect.onProcessPCMFrames = ma_engine_effect__on_process_pcm_frames;
pEffect->baseEffect.onGetRequiredInputFrameCount = ma_engine_effect__on_get_required_input_frame_count;
pEffect->baseEffect.onGetExpectedOutputFrameCount = ma_engine_effect__on_get_expected_output_frame_count;
pEffect->baseEffect.onGetInputDataFormat = ma_engine_effect__on_get_input_data_format;
pEffect->baseEffect.onGetOutputDataFormat = ma_engine_effect__on_get_output_data_format;
/*
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).
pannerConfig = ma_panner_config_init(pEngine->format, pEngine->channels);
result = ma_panner_init(&pannerConfig, &pEffect->panner);
if (result != MA_SUCCESS) {
return result; /* Failed to create the panner. */
}
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. */
spatializerConfig = ma_spatializer_config_init(pEngine, pEngine->format, pEngine->channels);
result = ma_spatializer_init(&spatializerConfig, &pEffect->spatializer);
if (result != MA_SUCCESS) {
return result; /* Failed to create the spatializer. */
}
/* Make a new head. */
c89atomic_exchange_ptr(&pGroup->pFirstSoundInGroup, pSound->pNextSoundInGroup);
} else {
/*
The sound is not the head. We need to remove the sound from the group by simply changing the pNextSoundInGroup member of the previous sound. This is
the important part. This is the part that allows the mixing thread to continue iteration without locking.
*/
c89atomic_exchange_ptr(&pSound->pPrevSoundInGroup->pNextSoundInGroup, pSound->pNextSoundInGroup);
}
faderConfig = ma_fader_config_init(pEngine->format, pEngine->channels, pEngine->sampleRate);
result = ma_fader_init(&faderConfig, &pEffect->fader);
if (result != MA_SUCCESS) {
return result; /* Failed to create the fader. */
/* This doesn't really need to be done atomically because we've wrapped this in a lock and it's not used by the mixing thread. */
if (pSound->pNextSoundInGroup != NULL) {
c89atomic_exchange_ptr(&pSound->pNextSoundInGroup->pPrevSoundInGroup, pSound->pPrevSoundInGroup);
}
}
ma_mutex_unlock(&pGroup->lock);
return MA_SUCCESS;
}
/* Our effect processor requires f32 for now, but I may implement an s16 optimized pipeline. */
static ma_result ma_engine_sound_attach(ma_engine* pEngine, ma_sound* pSound, ma_sound_group* pGroup)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pSound != NULL);
MA_ASSERT(pGroup != NULL);
MA_ASSERT(pSound->pGroup == NULL);
converterConfig = ma_data_converter_config_init(pEngine->format, pEngine->format, pEngine->channels, pEngine->channels, pEngine->sampleRate, pEngine->sampleRate);
/* 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. */
converterConfig.resampling.algorithm = ma_resample_algorithm_linear;
converterConfig.resampling.linear.lpfOrder = 0;
ma_mutex_lock(&pGroup->lock);
{
ma_sound* pNewFirstSoundInGroup = pSound;
ma_sound* pOldFirstSoundInGroup = pGroup->pFirstSoundInGroup;
result = ma_data_converter_init(&converterConfig, &pEffect->converter);
if (result != MA_SUCCESS) {
return result;
pNewFirstSoundInGroup->pNextSoundInGroup = pOldFirstSoundInGroup;
if (pOldFirstSoundInGroup != NULL) {
pOldFirstSoundInGroup->pPrevSoundInGroup = pNewFirstSoundInGroup;
}
c89atomic_exchange_ptr(&pGroup->pFirstSoundInGroup, pNewFirstSoundInGroup);
}
ma_mutex_unlock(&pGroup->lock);
return MA_SUCCESS;
}
static void ma_engine_effect_uninit(ma_engine* pEngine, ma_engine_effect* pEffect)
{
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEffect != NULL);
(void)pEngine;
ma_data_converter_uninit(&pEffect->converter);
}
static ma_result ma_engine_effect_reinit(ma_engine* pEngine, ma_engine_effect* pEffect)
{
/* This function assumes the data converter was previously initialized and needs to be uninitialized. */
MA_ASSERT(pEngine != NULL);
MA_ASSERT(pEffect != NULL);
ma_engine_effect_uninit(pEngine, pEffect);
return ma_engine_effect_init(pEngine, pEffect);
}
static ma_result ma_engine_sound_init_from_data_source_internal(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound)
{
......
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