Commit 1982364b authored by David Reid's avatar David Reid

Minor rearrangement of some code.

parent e13f00b3
...@@ -304,34 +304,6 @@ job will be posted back onto the job queue for later processing. When the job fi ...@@ -304,34 +304,6 @@ job will be posted back onto the job queue for later processing. When the job fi
system means the no matter how many job threads are executing, decoding of an individual sound will always get processed serially. The advantage to having system means the no matter how many job threads are executing, decoding of an individual sound will always get processed serially. The advantage to having
multiple threads comes into play when loading multiple sounds at the time time. multiple threads comes into play when loading multiple sounds at the time time.
*/ */
typedef struct
{
ma_uint32 channels;
ma_uint32 smoothTimeInFrames;
} ma_gainer_config;
MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames);
typedef struct
{
ma_gainer_config config;
ma_uint32 t;
float* pOldGains;
float* pNewGains;
/* Memory management. */
void* _pHeap;
ma_bool32 _ownsHeap;
} ma_gainer;
MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes);
MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer);
MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer);
MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks);
MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain);
MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains);
/* /*
...@@ -1529,6 +1501,38 @@ typedef struct ma_engine ma_engine; ...@@ -1529,6 +1501,38 @@ typedef struct ma_engine ma_engine;
typedef struct ma_sound ma_sound; typedef struct ma_sound ma_sound;
/* Gainer for smooth volume changes. */
typedef struct
{
ma_uint32 channels;
ma_uint32 smoothTimeInFrames;
} ma_gainer_config;
MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames);
typedef struct
{
ma_gainer_config config;
ma_uint32 t;
float* pOldGains;
float* pNewGains;
/* Memory management. */
void* _pHeap;
ma_bool32 _ownsHeap;
} ma_gainer;
MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes);
MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer);
MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer);
MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks);
MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount);
MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain);
MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains);
/* Stereo panner. */ /* Stereo panner. */
typedef enum typedef enum
{ {
...@@ -2286,658 +2290,386 @@ MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode); ...@@ -2286,658 +2290,386 @@ MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode);
#if defined(MA_IMPLEMENTATION) || defined(MINIAUDIO_IMPLEMENTATION) #if defined(MA_IMPLEMENTATION) || defined(MINIAUDIO_IMPLEMENTATION)
/* 10ms @ 48K = 480. Must never exceed 65535. */
#ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS
#define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480
#endif
MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames)
static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime);
MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
{ {
ma_gainer_config config; #ifndef MA_NO_GENERATION
{
ma_waveform_config waveformConfig;
ma_waveform waveform;
MA_ZERO_OBJECT(&config); waveformConfig = ma_waveform_config_init(format, channels, sampleRate, ma_waveform_type_sine, 1.0, 400);
config.channels = channels; ma_waveform_init(&waveformConfig, &waveform);
config.smoothTimeInFrames = smoothTimeInFrames; ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount);
}
#else
{
(void)pFramesOut;
(void)frameCount;
(void)format;
(void)channels;
(void)sampleRate;
#if defined(MA_DEBUG_OUTPUT)
{
#warning ma_debug_fill_pcm_frames_with_sine_wave() will do nothing because MA_NO_GENERATION is enabled.
}
#endif
}
#endif
}
return config;
static MA_INLINE ma_int16 ma_float_to_fixed_16(float x)
{
return (ma_int16)(x * (1 << 8));
} }
typedef struct static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume)
{ {
size_t sizeInBytes; return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8);
size_t oldGainsOffset; }
size_t newGainsOffset;
} ma_gainer_heap_layout;
static ma_result ma_gainer_get_heap_layout(const ma_gainer_config* pConfig, ma_gainer_heap_layout* pHeapLayout) static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume)
{ {
MA_ASSERT(pHeapLayout != NULL); return (ma_int32)((x * volume) >> 8);
}
MA_ZERO_OBJECT(pHeapLayout); static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume)
{
return (ma_int64)((x * volume) >> 8);
}
if (pConfig == NULL) { static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume)
return MA_INVALID_ARGS; {
} return (ma_int64)((x * volume) >> 8);
}
if (pConfig->channels == 0) { static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume)
{
return x * volume;
}
static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMapIn, ma_uint32 channelCountIn, const ma_channel* pChannelMapOut, ma_uint32 channelCountOut, ma_uint8* pShuffleTable)
{
ma_uint32 iChannelIn;
ma_uint32 iChannelOut;
if (pShuffleTable == NULL || channelCountIn == 0 || channelCountIn > MA_MAX_CHANNELS || channelCountOut == 0 || channelCountOut > MA_MAX_CHANNELS) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
pHeapLayout->sizeInBytes = 0; /*
When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the
input channel has more than one occurance of a channel position, the second one will be ignored.
*/
for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) {
ma_channel channelOut;
/* Old gains. */ /* Default to MA_CHANNEL_INDEX_NULL so that if a mapping is not found it'll be set appropriately. */
pHeapLayout->oldGainsOffset = pHeapLayout->sizeInBytes; pShuffleTable[iChannelOut] = MA_CHANNEL_INDEX_NULL;
pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels;
/* New gains. */ channelOut = ma_channel_map_get_channel(pChannelMapOut, channelCountOut, iChannelOut);
pHeapLayout->newGainsOffset = pHeapLayout->sizeInBytes; for (iChannelIn = 0; iChannelIn < channelCountIn; iChannelIn += 1) {
pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; ma_channel channelIn;
/* Alignment. */ channelIn = ma_channel_map_get_channel(pChannelMapIn, channelCountIn, iChannelIn);
pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); if (channelOut == channelIn) {
pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
break;
}
/*
Getting here means the channels don't exactly match, but we are going to support some
relaxed matching for practicality. If, for example, there are two stereo channel maps,
but one uses front left/right and the other uses side left/right, it makes logical
sense to just map these. The way we'll do it is we'll check if there is a logical
corresponding mapping, and if so, apply it, but we will *not* break from the loop,
thereby giving the loop a chance to find an exact match later which will take priority.
*/
switch (channelOut)
{
/* Left channels. */
case MA_CHANNEL_FRONT_LEFT:
case MA_CHANNEL_SIDE_LEFT:
{
switch (channelIn) {
case MA_CHANNEL_FRONT_LEFT:
case MA_CHANNEL_SIDE_LEFT:
{
pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
} break;
}
} break;
/* Right channels. */
case MA_CHANNEL_FRONT_RIGHT:
case MA_CHANNEL_SIDE_RIGHT:
{
switch (channelIn) {
case MA_CHANNEL_FRONT_RIGHT:
case MA_CHANNEL_SIDE_RIGHT:
{
pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
} break;
}
} break;
default: break;
}
}
}
return MA_SUCCESS; return MA_SUCCESS;
} }
static ma_result ma_channel_map_apply_shuffle_table_f32(float* pFramesOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable)
MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes)
{ {
ma_result result; ma_uint64 iFrame;
ma_gainer_heap_layout heapLayout; ma_uint32 iChannelOut;
if (pHeapSizeInBytes == NULL) { if (pFramesOut == NULL || pFramesIn == NULL || channelsOut == 0 || channelsOut > MA_MAX_CHANNELS || pShuffleTable == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
*pHeapSizeInBytes = 0; for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
ma_uint8 iChannelIn = pShuffleTable[iChannelOut];
if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */
pFramesOut[iChannelOut] = pFramesIn[iChannelIn];
} else {
pFramesOut[iChannelOut] = 0;
}
}
result = ma_gainer_get_heap_layout(pConfig, &heapLayout); pFramesOut += channelsOut;
if (result != MA_SUCCESS) { pFramesIn += channelsIn;
return MA_INVALID_ARGS;
} }
*pHeapSizeInBytes = heapLayout.sizeInBytes;
return MA_SUCCESS; return MA_SUCCESS;
} }
static ma_result ma_channel_map_apply_mono_out_f32(float* pFramesOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount)
MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer)
{ {
ma_result result; ma_uint64 iFrame;
ma_gainer_heap_layout heapLayout; ma_uint32 iChannelIn;
ma_uint32 iChannel; ma_uint32 accumulationCount;
if (pGainer == NULL) { if (pFramesOut == NULL || pFramesIn == NULL || channelsIn == 0) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
MA_ZERO_OBJECT(pGainer); /* In this case the output stream needs to be the average of all channels, ignoring NONE. */
if (pConfig == NULL || pHeap == NULL) { /* A quick pre-processing step to get the accumulation counter since we're ignoring NONE channels. */
return MA_INVALID_ARGS; accumulationCount = 0;
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
if (ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn) != MA_CHANNEL_NONE) {
accumulationCount += 1;
}
} }
result = ma_gainer_get_heap_layout(pConfig, &heapLayout); if (accumulationCount > 0) { /* <-- Prevent a division by zero. */
if (result != MA_SUCCESS) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
return result; float accumulation = 0;
}
pGainer->_pHeap = pHeap;
pGainer->pOldGains = (float*)ma_offset_ptr(pHeap, heapLayout.oldGainsOffset);
pGainer->pNewGains = (float*)ma_offset_ptr(pHeap, heapLayout.newGainsOffset);
pGainer->config = *pConfig; for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
pGainer->t = (ma_uint32)-1; /* No interpolation by default. */ ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
if (channelIn != MA_CHANNEL_NONE) {
accumulation += pFramesIn[iChannelIn];
}
}
for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { pFramesOut[0] = accumulation / accumulationCount;
pGainer->pOldGains[iChannel] = 1; pFramesOut += 1;
pGainer->pNewGains[iChannel] = 1; pFramesIn += channelsIn;
}
} else {
ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, 1);
} }
return MA_SUCCESS; return MA_SUCCESS;
} }
MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer) static ma_result ma_channel_map_apply_mono_in_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint64 frameCount)
{ {
ma_result result; ma_uint64 iFrame;
size_t heapSizeInBytes; ma_uint32 iChannelOut;
void* pHeap;
result = ma_gainer_get_heap_size(pConfig, &heapSizeInBytes); if (pFramesOut == NULL || channelsOut == 0 || pFramesIn == NULL) {
if (result != MA_SUCCESS) { return MA_INVALID_ARGS;
return result; /* Failed to retrieve the size of the heap allocation. */
} }
if (heapSizeInBytes > 0) { /* In this case we just copy the mono channel to each of the output channels, ignoring NONE. */
pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
if (pHeap == NULL) { for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
return MA_OUT_OF_MEMORY; ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
if (channelOut != MA_CHANNEL_NONE) {
pFramesOut[iChannelOut] = pFramesIn[0];
}
} }
} else {
pHeap = NULL;
}
result = ma_gainer_init_preallocated(pConfig, pHeap, pGainer); pFramesOut += channelsOut;
if (result != MA_SUCCESS) { pFramesIn += 1;
return result;
} }
pGainer->_ownsHeap = MA_TRUE;
return MA_SUCCESS; return MA_SUCCESS;
} }
MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks) static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode)
{ {
if (pGainer == NULL) { ma_bool32 passthrough = MA_FALSE;
if (channelsOut == channelsIn) {
if (pChannelMapOut == pChannelMapIn) {
passthrough = MA_TRUE;
} else {
if (ma_channel_map_equal(channelsOut, pChannelMapOut, pChannelMapIn)) {
passthrough = MA_TRUE;
}
}
}
/* Optimized Path: Passthrough */
if (passthrough) {
ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, channelsOut);
return; return;
} }
if (pGainer->_pHeap != NULL && pGainer->_ownsHeap) { /* Special Path: Mono Output. */
ma_free(pGainer->_pHeap, pAllocationCallbacks); if (channelsOut == 1 && (pChannelMapOut == NULL || pChannelMapOut[0] == MA_CHANNEL_MONO)) {
ma_channel_map_apply_mono_out_f32(pFramesOut, pFramesIn, pChannelMapIn, channelsIn, frameCount);
return;
} }
}
static float ma_gainer_calculate_current_gain(const ma_gainer* pGainer, ma_uint32 channel) /* Special Path: Mono Input. */
{ if (channelsIn == 1 && (pChannelMapIn == NULL || pChannelMapIn[0] == MA_CHANNEL_MONO)) {
float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; ma_channel_map_apply_mono_in_f32(pFramesOut, pChannelMapOut, channelsOut, pFramesIn, frameCount);
return ma_mix_f32_fast(pGainer->pOldGains[channel], pGainer->pNewGains[channel], a); return;
} }
MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
{
ma_uint64 iFrame;
ma_uint32 iChannel;
float* pFramesOutF32 = (float*)pFramesOut;
const float* pFramesInF32 = (const float*)pFramesIn;
if (pGainer == NULL) { if (channelsOut <= MA_MAX_CHANNELS) {
return MA_INVALID_ARGS; ma_result result;
}
if (pGainer->t >= pGainer->config.smoothTimeInFrames) { if (mode == ma_channel_mix_mode_simple) {
/* Fast path. No gain calculation required. */ ma_channel shuffleTable[MA_MAX_CHANNELS];
ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOutF32, pFramesInF32, frameCount, pGainer->config.channels, pGainer->pNewGains);
/* Now that some frames have been processed we need to make sure future changes to the gain are interpolated. */ result = ma_channel_map_build_shuffle_table(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, shuffleTable);
if (pGainer->t == (ma_uint32)-1) { if (result != MA_SUCCESS) {
pGainer->t = pGainer->config.smoothTimeInFrames; return;
} }
} else {
/* Slow path. Need to interpolate the gain for each channel individually. */
/* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ result = ma_channel_map_apply_shuffle_table_f32(pFramesOut, channelsOut, pFramesIn, channelsIn, frameCount, shuffleTable);
if (pFramesOut != NULL && pFramesIn != NULL) { if (result != MA_SUCCESS) {
float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; return;
float d = 1.0f / pGainer->config.smoothTimeInFrames; }
ma_uint32 channelCount = pGainer->config.channels; } else {
ma_uint32 iFrame;
ma_uint32 iChannelOut;
ma_uint32 iChannelIn;
float weights[32][32]; /* Do not use MA_MAX_CHANNELS here! */
for (iFrame = 0; iFrame < frameCount; iFrame += 1) { /*
for (iChannel = 0; iChannel < channelCount; iChannel += 1) { If we have a small enough number of channels, pre-compute the weights. Otherwise we'll just need to
pFramesOutF32[iChannel] = pFramesInF32[iChannel] * ma_mix_f32_fast(pGainer->pOldGains[iChannel], pGainer->pNewGains[iChannel], a); fall back to a slower path because otherwise we'll run out of stack space.
*/
if (channelsIn <= ma_countof(weights) && channelsOut <= ma_countof(weights)) {
/* Pre-compute weights. */
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
weights[iChannelOut][iChannelIn] = ma_calculate_channel_position_rectangular_weight(channelOut, channelIn);
}
} }
pFramesOutF32 += channelCount; for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
pFramesInF32 += channelCount; for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
float accumulation = 0;
a += d; for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
if (a > 1) { accumulation += pFramesIn[iChannelIn] * weights[iChannelOut][iChannelIn];
a = 1; }
pFramesOut[iChannelOut] = accumulation;
}
pFramesOut += channelsOut;
pFramesIn += channelsIn;
} }
} } else {
} /* Cannot pre-compute weights because not enough room in stack-allocated buffer. */
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
float accumulation = 0;
ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
pGainer->t = (ma_uint32)ma_min(pGainer->t + frameCount, pGainer->config.smoothTimeInFrames); for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
accumulation += pFramesIn[iChannelIn] * ma_calculate_channel_position_rectangular_weight(channelOut, channelIn);
}
#if 0 /* Reference implementation. */ pFramesOut[iChannelOut] = accumulation;
for (iFrame = 0; iFrame < frameCount; iFrame += 1) { }
/* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */
if (pFramesOut != NULL && pFramesIn != NULL) { pFramesOut += channelsOut;
for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { pFramesIn += channelsIn;
pFramesOutF32[iFrame*pGainer->config.channels + iChannel] = pFramesInF32[iFrame*pGainer->config.channels + iChannel] * ma_gainer_calculate_current_gain(pGainer, iChannel);
} }
} }
/* Move interpolation time forward, but don't go beyond our smoothing time. */
pGainer->t = ma_min(pGainer->t + 1, pGainer->config.smoothTimeInFrames);
} }
#endif } else {
/* Fall back to silence. If you hit this, what are you doing with so many channels?! */
ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, channelsOut);
} }
return MA_SUCCESS;
} }
static void ma_gainer_set_gain_by_index(ma_gainer* pGainer, float newGain, ma_uint32 iChannel)
{
pGainer->pOldGains[iChannel] = ma_gainer_calculate_current_gain(pGainer, iChannel);
pGainer->pNewGains[iChannel] = newGain;
}
static void ma_gainer_reset_smoothing_time(ma_gainer* pGainer)
{
if (pGainer->t == (ma_uint32)-1) {
pGainer->t = pGainer->config.smoothTimeInFrames; /* No smoothing required for initial gains setting. */
} else {
pGainer->t = 0;
}
}
MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain) static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
{ {
ma_uint32 iChannel; ma_uint64 iSample;
ma_uint64 sampleCount;
if (pGainer == NULL) { if (pDst == NULL || pSrc == NULL || channels == 0) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { if (volume == 0) {
ma_gainer_set_gain_by_index(pGainer, newGain, iChannel); return MA_SUCCESS; /* No changes if the volume is 0. */
} }
/* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ sampleCount = frameCount * channels;
ma_gainer_reset_smoothing_time(pGainer);
return MA_SUCCESS;
}
MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains)
{
ma_uint32 iChannel;
if (pGainer == NULL || pNewGains == NULL) {
return MA_INVALID_ARGS;
}
for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { if (volume == 1) {
ma_gainer_set_gain_by_index(pGainer, pNewGains[iChannel], iChannel); for (iSample = 0; iSample < sampleCount; iSample += 1) {
pDst[iSample] += pSrc[iSample];
}
} else {
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume);
}
} }
/* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */
ma_gainer_reset_smoothing_time(pGainer);
return MA_SUCCESS; return MA_SUCCESS;
} }
MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels)
{
/* 10ms @ 48K = 480. Must never exceed 65535. */ ma_node_graph_config config;
#ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS
#define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480
#endif
static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime);
MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate)
{
#ifndef MA_NO_GENERATION
{
ma_waveform_config waveformConfig;
ma_waveform waveform;
waveformConfig = ma_waveform_config_init(format, channels, sampleRate, ma_waveform_type_sine, 1.0, 400);
ma_waveform_init(&waveformConfig, &waveform);
ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount);
}
#else
{
(void)pFramesOut;
(void)frameCount;
(void)format;
(void)channels;
(void)sampleRate;
#if defined(MA_DEBUG_OUTPUT)
{
#warning ma_debug_fill_pcm_frames_with_sine_wave() will do nothing because MA_NO_GENERATION is enabled.
}
#endif
}
#endif
}
static MA_INLINE ma_int16 ma_float_to_fixed_16(float x)
{
return (ma_int16)(x * (1 << 8));
}
static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume)
{
return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8);
}
static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume)
{
return (ma_int32)((x * volume) >> 8);
}
static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume)
{
return (ma_int64)((x * volume) >> 8);
}
static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume)
{
return (ma_int64)((x * volume) >> 8);
}
static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume)
{
return x * volume;
}
static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMapIn, ma_uint32 channelCountIn, const ma_channel* pChannelMapOut, ma_uint32 channelCountOut, ma_uint8* pShuffleTable)
{
ma_uint32 iChannelIn;
ma_uint32 iChannelOut;
if (pShuffleTable == NULL || channelCountIn == 0 || channelCountIn > MA_MAX_CHANNELS || channelCountOut == 0 || channelCountOut > MA_MAX_CHANNELS) {
return MA_INVALID_ARGS;
}
/*
When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the
input channel has more than one occurance of a channel position, the second one will be ignored.
*/
for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) {
ma_channel channelOut;
/* Default to MA_CHANNEL_INDEX_NULL so that if a mapping is not found it'll be set appropriately. */
pShuffleTable[iChannelOut] = MA_CHANNEL_INDEX_NULL;
channelOut = ma_channel_map_get_channel(pChannelMapOut, channelCountOut, iChannelOut);
for (iChannelIn = 0; iChannelIn < channelCountIn; iChannelIn += 1) {
ma_channel channelIn;
channelIn = ma_channel_map_get_channel(pChannelMapIn, channelCountIn, iChannelIn);
if (channelOut == channelIn) {
pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
break;
}
/*
Getting here means the channels don't exactly match, but we are going to support some
relaxed matching for practicality. If, for example, there are two stereo channel maps,
but one uses front left/right and the other uses side left/right, it makes logical
sense to just map these. The way we'll do it is we'll check if there is a logical
corresponding mapping, and if so, apply it, but we will *not* break from the loop,
thereby giving the loop a chance to find an exact match later which will take priority.
*/
switch (channelOut)
{
/* Left channels. */
case MA_CHANNEL_FRONT_LEFT:
case MA_CHANNEL_SIDE_LEFT:
{
switch (channelIn) {
case MA_CHANNEL_FRONT_LEFT:
case MA_CHANNEL_SIDE_LEFT:
{
pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
} break;
}
} break;
/* Right channels. */
case MA_CHANNEL_FRONT_RIGHT:
case MA_CHANNEL_SIDE_RIGHT:
{
switch (channelIn) {
case MA_CHANNEL_FRONT_RIGHT:
case MA_CHANNEL_SIDE_RIGHT:
{
pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn;
} break;
}
} break;
default: break;
}
}
}
return MA_SUCCESS;
}
static ma_result ma_channel_map_apply_shuffle_table_f32(float* pFramesOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable)
{
ma_uint64 iFrame;
ma_uint32 iChannelOut;
if (pFramesOut == NULL || pFramesIn == NULL || channelsOut == 0 || channelsOut > MA_MAX_CHANNELS || pShuffleTable == NULL) {
return MA_INVALID_ARGS;
}
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
ma_uint8 iChannelIn = pShuffleTable[iChannelOut];
if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */
pFramesOut[iChannelOut] = pFramesIn[iChannelIn];
} else {
pFramesOut[iChannelOut] = 0;
}
}
pFramesOut += channelsOut;
pFramesIn += channelsIn;
}
return MA_SUCCESS;
}
static ma_result ma_channel_map_apply_mono_out_f32(float* pFramesOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount)
{
ma_uint64 iFrame;
ma_uint32 iChannelIn;
ma_uint32 accumulationCount;
if (pFramesOut == NULL || pFramesIn == NULL || channelsIn == 0) {
return MA_INVALID_ARGS;
}
/* In this case the output stream needs to be the average of all channels, ignoring NONE. */
/* A quick pre-processing step to get the accumulation counter since we're ignoring NONE channels. */
accumulationCount = 0;
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
if (ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn) != MA_CHANNEL_NONE) {
accumulationCount += 1;
}
}
if (accumulationCount > 0) { /* <-- Prevent a division by zero. */
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
float accumulation = 0;
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
if (channelIn != MA_CHANNEL_NONE) {
accumulation += pFramesIn[iChannelIn];
}
}
pFramesOut[0] = accumulation / accumulationCount;
pFramesOut += 1;
pFramesIn += channelsIn;
}
} else {
ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, 1);
}
return MA_SUCCESS;
}
static ma_result ma_channel_map_apply_mono_in_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint64 frameCount)
{
ma_uint64 iFrame;
ma_uint32 iChannelOut;
if (pFramesOut == NULL || channelsOut == 0 || pFramesIn == NULL) {
return MA_INVALID_ARGS;
}
/* In this case we just copy the mono channel to each of the output channels, ignoring NONE. */
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
if (channelOut != MA_CHANNEL_NONE) {
pFramesOut[iChannelOut] = pFramesIn[0];
}
}
pFramesOut += channelsOut;
pFramesIn += 1;
}
return MA_SUCCESS;
}
static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode)
{
ma_bool32 passthrough = MA_FALSE;
if (channelsOut == channelsIn) {
if (pChannelMapOut == pChannelMapIn) {
passthrough = MA_TRUE;
} else {
if (ma_channel_map_equal(channelsOut, pChannelMapOut, pChannelMapIn)) {
passthrough = MA_TRUE;
}
}
}
/* Optimized Path: Passthrough */
if (passthrough) {
ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, channelsOut);
return;
}
/* Special Path: Mono Output. */
if (channelsOut == 1 && (pChannelMapOut == NULL || pChannelMapOut[0] == MA_CHANNEL_MONO)) {
ma_channel_map_apply_mono_out_f32(pFramesOut, pFramesIn, pChannelMapIn, channelsIn, frameCount);
return;
}
/* Special Path: Mono Input. */
if (channelsIn == 1 && (pChannelMapIn == NULL || pChannelMapIn[0] == MA_CHANNEL_MONO)) {
ma_channel_map_apply_mono_in_f32(pFramesOut, pChannelMapOut, channelsOut, pFramesIn, frameCount);
return;
}
if (channelsOut <= MA_MAX_CHANNELS) {
ma_result result;
if (mode == ma_channel_mix_mode_simple) {
ma_channel shuffleTable[MA_MAX_CHANNELS];
result = ma_channel_map_build_shuffle_table(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, shuffleTable);
if (result != MA_SUCCESS) {
return;
}
result = ma_channel_map_apply_shuffle_table_f32(pFramesOut, channelsOut, pFramesIn, channelsIn, frameCount, shuffleTable);
if (result != MA_SUCCESS) {
return;
}
} else {
ma_uint32 iFrame;
ma_uint32 iChannelOut;
ma_uint32 iChannelIn;
float weights[32][32]; /* Do not use MA_MAX_CHANNELS here! */
/*
If we have a small enough number of channels, pre-compute the weights. Otherwise we'll just need to
fall back to a slower path because otherwise we'll run out of stack space.
*/
if (channelsIn <= ma_countof(weights) && channelsOut <= ma_countof(weights)) {
/* Pre-compute weights. */
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
weights[iChannelOut][iChannelIn] = ma_calculate_channel_position_rectangular_weight(channelOut, channelIn);
}
}
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
float accumulation = 0;
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
accumulation += pFramesIn[iChannelIn] * weights[iChannelOut][iChannelIn];
}
pFramesOut[iChannelOut] = accumulation;
}
pFramesOut += channelsOut;
pFramesIn += channelsIn;
}
} else {
/* Cannot pre-compute weights because not enough room in stack-allocated buffer. */
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) {
float accumulation = 0;
ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut);
for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) {
ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn);
accumulation += pFramesIn[iChannelIn] * ma_calculate_channel_position_rectangular_weight(channelOut, channelIn);
}
pFramesOut[iChannelOut] = accumulation;
}
pFramesOut += channelsOut;
pFramesIn += channelsIn;
}
}
}
} else {
/* Fall back to silence. If you hit this, what are you doing with so many channels?! */
ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, channelsOut);
}
}
static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume)
{
ma_uint64 iSample;
ma_uint64 sampleCount;
if (pDst == NULL || pSrc == NULL || channels == 0) {
return MA_INVALID_ARGS;
}
if (volume == 0) {
return MA_SUCCESS; /* No changes if the volume is 0. */
}
sampleCount = frameCount * channels;
if (volume == 1) {
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pDst[iSample] += pSrc[iSample];
}
} else {
for (iSample = 0; iSample < sampleCount; iSample += 1) {
pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume);
}
}
return MA_SUCCESS;
}
MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels)
{
ma_node_graph_config config;
MA_ZERO_OBJECT(&config); MA_ZERO_OBJECT(&config);
config.channels = channels; config.channels = channels;
...@@ -9140,152 +8872,420 @@ static ma_result ma_resource_manager_process_job__free_data_stream(ma_resource_m ...@@ -9140,152 +8872,420 @@ static ma_result ma_resource_manager_process_job__free_data_stream(ma_resource_m
pDataStream = pJob->freeDataStream.pDataStream; pDataStream = pJob->freeDataStream.pDataStream;
MA_ASSERT(pDataStream != NULL); MA_ASSERT(pDataStream != NULL);
/* If our status is not MA_UNAVAILABLE we have a bug somewhere. */ /* If our status is not MA_UNAVAILABLE we have a bug somewhere. */
MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) == MA_UNAVAILABLE); MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) == MA_UNAVAILABLE);
if (pJob->order != pDataStream->executionPointer) {
return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */
}
if (pDataStream->isDecoderInitialized) {
ma_decoder_uninit(&pDataStream->decoder);
}
if (pDataStream->pPageData != NULL) {
ma_free(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks);
pDataStream->pPageData = NULL; /* Just in case... */
}
ma_data_source_uninit(&pDataStream->ds);
/* The event needs to be signalled last. */
if (pJob->freeDataStream.pDoneNotification != NULL) {
ma_async_notification_signal(pJob->freeDataStream.pDoneNotification);
}
if (pJob->freeDataStream.pDoneFence != NULL) {
ma_fence_release(pJob->freeDataStream.pDoneFence);
}
/*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/
return MA_SUCCESS;
}
static ma_result ma_resource_manager_process_job__page_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
{
ma_result result = MA_SUCCESS;
ma_resource_manager_data_stream* pDataStream;
MA_ASSERT(pResourceManager != NULL);
MA_ASSERT(pJob != NULL);
pDataStream = pJob->pageDataStream.pDataStream;
MA_ASSERT(pDataStream != NULL);
/* For streams, the status should be MA_SUCCESS. */
if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) {
result = MA_INVALID_OPERATION;
goto done;
}
if (pJob->order != pDataStream->executionPointer) {
return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */
}
ma_resource_manager_data_stream_fill_page(pDataStream, pJob->pageDataStream.pageIndex);
done:
c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
return result;
}
static ma_result ma_resource_manager_process_job__seek_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
{
ma_result result = MA_SUCCESS;
ma_resource_manager_data_stream* pDataStream;
MA_ASSERT(pResourceManager != NULL);
MA_ASSERT(pJob != NULL);
pDataStream = pJob->seekDataStream.pDataStream;
MA_ASSERT(pDataStream != NULL);
/* For streams the status should be MA_SUCCESS for this to do anything. */
if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) {
result = MA_INVALID_OPERATION;
goto done;
}
if (pJob->order != pDataStream->executionPointer) {
return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */
}
/*
With seeking we just assume both pages are invalid and the relative frame cursor at at position 0. This is basically exactly the same as loading, except
instead of initializing the decoder, we seek to a frame.
*/
ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->seekDataStream.frameIndex);
/* After seeking we'll need to reload the pages. */
ma_resource_manager_data_stream_fill_pages(pDataStream);
/* We need to let the public API know that we're done seeking. */
c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1);
done:
c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);
return result;
}
MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob)
{
if (pResourceManager == NULL || pJob == NULL) {
return MA_INVALID_ARGS;
}
switch (pJob->toc.breakup.code)
{
/* Data Buffer Node */
case MA_JOB_LOAD_DATA_BUFFER_NODE: return ma_resource_manager_process_job__load_data_buffer_node(pResourceManager, pJob);
case MA_JOB_FREE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__free_data_buffer_node(pResourceManager, pJob);
case MA_JOB_PAGE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__page_data_buffer_node(pResourceManager, pJob);
/* Data Buffer */
case MA_JOB_LOAD_DATA_BUFFER: return ma_resource_manager_process_job__load_data_buffer(pResourceManager, pJob);
case MA_JOB_FREE_DATA_BUFFER: return ma_resource_manager_process_job__free_data_buffer(pResourceManager, pJob);
/* Data Stream */
case MA_JOB_LOAD_DATA_STREAM: return ma_resource_manager_process_job__load_data_stream(pResourceManager, pJob);
case MA_JOB_FREE_DATA_STREAM: return ma_resource_manager_process_job__free_data_stream(pResourceManager, pJob);
case MA_JOB_PAGE_DATA_STREAM: return ma_resource_manager_process_job__page_data_stream(pResourceManager, pJob);
case MA_JOB_SEEK_DATA_STREAM: return ma_resource_manager_process_job__seek_data_stream(pResourceManager, pJob);
default: break;
}
/* Getting here means we don't know what the job code is and cannot do anything with it. */
return MA_INVALID_OPERATION;
}
MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager)
{
ma_result result;
ma_job job;
if (pResourceManager == NULL) {
return MA_INVALID_ARGS;
}
/* This will return MA_CANCELLED if the next job is a quit job. */
result = ma_resource_manager_next_job(pResourceManager, &job);
if (result != MA_SUCCESS) {
return result;
}
return ma_resource_manager_process_job(pResourceManager, &job);
}
MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames)
{
ma_gainer_config config;
MA_ZERO_OBJECT(&config);
config.channels = channels;
config.smoothTimeInFrames = smoothTimeInFrames;
return config;
}
typedef struct
{
size_t sizeInBytes;
size_t oldGainsOffset;
size_t newGainsOffset;
} ma_gainer_heap_layout;
static ma_result ma_gainer_get_heap_layout(const ma_gainer_config* pConfig, ma_gainer_heap_layout* pHeapLayout)
{
MA_ASSERT(pHeapLayout != NULL);
MA_ZERO_OBJECT(pHeapLayout);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
if (pConfig->channels == 0) {
return MA_INVALID_ARGS;
}
pHeapLayout->sizeInBytes = 0;
/* Old gains. */
pHeapLayout->oldGainsOffset = pHeapLayout->sizeInBytes;
pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels;
/* New gains. */
pHeapLayout->newGainsOffset = pHeapLayout->sizeInBytes;
pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels;
/* Alignment. */
pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes);
return MA_SUCCESS;
}
if (pJob->order != pDataStream->executionPointer) {
return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */
}
if (pDataStream->isDecoderInitialized) { MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes)
ma_decoder_uninit(&pDataStream->decoder); {
} ma_result result;
ma_gainer_heap_layout heapLayout;
if (pDataStream->pPageData != NULL) { if (pHeapSizeInBytes == NULL) {
ma_free(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks); return MA_INVALID_ARGS;
pDataStream->pPageData = NULL; /* Just in case... */
} }
ma_data_source_uninit(&pDataStream->ds); *pHeapSizeInBytes = 0;
/* The event needs to be signalled last. */ result = ma_gainer_get_heap_layout(pConfig, &heapLayout);
if (pJob->freeDataStream.pDoneNotification != NULL) { if (result != MA_SUCCESS) {
ma_async_notification_signal(pJob->freeDataStream.pDoneNotification); return MA_INVALID_ARGS;
}
if (pJob->freeDataStream.pDoneFence != NULL) {
ma_fence_release(pJob->freeDataStream.pDoneFence);
} }
/*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/ *pHeapSizeInBytes = heapLayout.sizeInBytes;
return MA_SUCCESS; return MA_SUCCESS;
} }
static ma_result ma_resource_manager_process_job__page_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob)
MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer)
{ {
ma_result result = MA_SUCCESS; ma_result result;
ma_resource_manager_data_stream* pDataStream; ma_gainer_heap_layout heapLayout;
ma_uint32 iChannel;
MA_ASSERT(pResourceManager != NULL); if (pGainer == NULL) {
MA_ASSERT(pJob != NULL); return MA_INVALID_ARGS;
}
pDataStream = pJob->pageDataStream.pDataStream; MA_ZERO_OBJECT(pGainer);
MA_ASSERT(pDataStream != NULL);
/* For streams, the status should be MA_SUCCESS. */ if (pConfig == NULL || pHeap == NULL) {
if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { return MA_INVALID_ARGS;
result = MA_INVALID_OPERATION;
goto done;
} }
if (pJob->order != pDataStream->executionPointer) { result = ma_gainer_get_heap_layout(pConfig, &heapLayout);
return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ if (result != MA_SUCCESS) {
return result;
} }
ma_resource_manager_data_stream_fill_page(pDataStream, pJob->pageDataStream.pageIndex); pGainer->_pHeap = pHeap;
pGainer->pOldGains = (float*)ma_offset_ptr(pHeap, heapLayout.oldGainsOffset);
pGainer->pNewGains = (float*)ma_offset_ptr(pHeap, heapLayout.newGainsOffset);
done: pGainer->config = *pConfig;
c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); pGainer->t = (ma_uint32)-1; /* No interpolation by default. */
return result;
for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) {
pGainer->pOldGains[iChannel] = 1;
pGainer->pNewGains[iChannel] = 1;
}
return MA_SUCCESS;
} }
static ma_result ma_resource_manager_process_job__seek_data_stream(ma_resource_manager* pResourceManager, ma_job* pJob) MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer)
{ {
ma_result result = MA_SUCCESS; ma_result result;
ma_resource_manager_data_stream* pDataStream; size_t heapSizeInBytes;
void* pHeap;
MA_ASSERT(pResourceManager != NULL);
MA_ASSERT(pJob != NULL);
pDataStream = pJob->seekDataStream.pDataStream; result = ma_gainer_get_heap_size(pConfig, &heapSizeInBytes);
MA_ASSERT(pDataStream != NULL); if (result != MA_SUCCESS) {
return result; /* Failed to retrieve the size of the heap allocation. */
}
/* For streams the status should be MA_SUCCESS for this to do anything. */ if (heapSizeInBytes > 0) {
if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) { pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks);
result = MA_INVALID_OPERATION; if (pHeap == NULL) {
goto done; return MA_OUT_OF_MEMORY;
}
} else {
pHeap = NULL;
} }
if (pJob->order != pDataStream->executionPointer) { result = ma_gainer_init_preallocated(pConfig, pHeap, pGainer);
return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ if (result != MA_SUCCESS) {
return result;
} }
/* pGainer->_ownsHeap = MA_TRUE;
With seeking we just assume both pages are invalid and the relative frame cursor at at position 0. This is basically exactly the same as loading, except return MA_SUCCESS;
instead of initializing the decoder, we seek to a frame. }
*/
ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->seekDataStream.frameIndex);
/* After seeking we'll need to reload the pages. */ MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks)
ma_resource_manager_data_stream_fill_pages(pDataStream); {
if (pGainer == NULL) {
return;
}
/* We need to let the public API know that we're done seeking. */ if (pGainer->_pHeap != NULL && pGainer->_ownsHeap) {
c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1); ma_free(pGainer->_pHeap, pAllocationCallbacks);
}
}
done: static float ma_gainer_calculate_current_gain(const ma_gainer* pGainer, ma_uint32 channel)
c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); {
return result; float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames;
return ma_mix_f32_fast(pGainer->pOldGains[channel], pGainer->pNewGains[channel], a);
} }
MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob) MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount)
{ {
if (pResourceManager == NULL || pJob == NULL) { ma_uint64 iFrame;
ma_uint32 iChannel;
float* pFramesOutF32 = (float*)pFramesOut;
const float* pFramesInF32 = (const float*)pFramesIn;
if (pGainer == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
switch (pJob->toc.breakup.code) if (pGainer->t >= pGainer->config.smoothTimeInFrames) {
{ /* Fast path. No gain calculation required. */
/* Data Buffer Node */ ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOutF32, pFramesInF32, frameCount, pGainer->config.channels, pGainer->pNewGains);
case MA_JOB_LOAD_DATA_BUFFER_NODE: return ma_resource_manager_process_job__load_data_buffer_node(pResourceManager, pJob);
case MA_JOB_FREE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__free_data_buffer_node(pResourceManager, pJob);
case MA_JOB_PAGE_DATA_BUFFER_NODE: return ma_resource_manager_process_job__page_data_buffer_node(pResourceManager, pJob);
/* Data Buffer */ /* Now that some frames have been processed we need to make sure future changes to the gain are interpolated. */
case MA_JOB_LOAD_DATA_BUFFER: return ma_resource_manager_process_job__load_data_buffer(pResourceManager, pJob); if (pGainer->t == (ma_uint32)-1) {
case MA_JOB_FREE_DATA_BUFFER: return ma_resource_manager_process_job__free_data_buffer(pResourceManager, pJob); pGainer->t = pGainer->config.smoothTimeInFrames;
}
} else {
/* Slow path. Need to interpolate the gain for each channel individually. */
/* Data Stream */ /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */
case MA_JOB_LOAD_DATA_STREAM: return ma_resource_manager_process_job__load_data_stream(pResourceManager, pJob); if (pFramesOut != NULL && pFramesIn != NULL) {
case MA_JOB_FREE_DATA_STREAM: return ma_resource_manager_process_job__free_data_stream(pResourceManager, pJob); float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames;
case MA_JOB_PAGE_DATA_STREAM: return ma_resource_manager_process_job__page_data_stream(pResourceManager, pJob); float d = 1.0f / pGainer->config.smoothTimeInFrames;
case MA_JOB_SEEK_DATA_STREAM: return ma_resource_manager_process_job__seek_data_stream(pResourceManager, pJob); ma_uint32 channelCount = pGainer->config.channels;
default: break; for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
for (iChannel = 0; iChannel < channelCount; iChannel += 1) {
pFramesOutF32[iChannel] = pFramesInF32[iChannel] * ma_mix_f32_fast(pGainer->pOldGains[iChannel], pGainer->pNewGains[iChannel], a);
}
pFramesOutF32 += channelCount;
pFramesInF32 += channelCount;
a += d;
if (a > 1) {
a = 1;
}
}
}
pGainer->t = (ma_uint32)ma_min(pGainer->t + frameCount, pGainer->config.smoothTimeInFrames);
#if 0 /* Reference implementation. */
for (iFrame = 0; iFrame < frameCount; iFrame += 1) {
/* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */
if (pFramesOut != NULL && pFramesIn != NULL) {
for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
pFramesOutF32[iFrame*pGainer->config.channels + iChannel] = pFramesInF32[iFrame*pGainer->config.channels + iChannel] * ma_gainer_calculate_current_gain(pGainer, iChannel);
}
}
/* Move interpolation time forward, but don't go beyond our smoothing time. */
pGainer->t = ma_min(pGainer->t + 1, pGainer->config.smoothTimeInFrames);
}
#endif
} }
/* Getting here means we don't know what the job code is and cannot do anything with it. */ return MA_SUCCESS;
return MA_INVALID_OPERATION;
} }
MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager) static void ma_gainer_set_gain_by_index(ma_gainer* pGainer, float newGain, ma_uint32 iChannel)
{ {
ma_result result; pGainer->pOldGains[iChannel] = ma_gainer_calculate_current_gain(pGainer, iChannel);
ma_job job; pGainer->pNewGains[iChannel] = newGain;
}
if (pResourceManager == NULL) { static void ma_gainer_reset_smoothing_time(ma_gainer* pGainer)
{
if (pGainer->t == (ma_uint32)-1) {
pGainer->t = pGainer->config.smoothTimeInFrames; /* No smoothing required for initial gains setting. */
} else {
pGainer->t = 0;
}
}
MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain)
{
ma_uint32 iChannel;
if (pGainer == NULL) {
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
/* This will return MA_CANCELLED if the next job is a quit job. */ for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
result = ma_resource_manager_next_job(pResourceManager, &job); ma_gainer_set_gain_by_index(pGainer, newGain, iChannel);
if (result != MA_SUCCESS) {
return result;
} }
return ma_resource_manager_process_job(pResourceManager, &job); /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */
ma_gainer_reset_smoothing_time(pGainer);
return MA_SUCCESS;
} }
MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains)
{
ma_uint32 iChannel;
if (pGainer == NULL || pNewGains == NULL) {
return MA_INVALID_ARGS;
}
for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) {
ma_gainer_set_gain_by_index(pGainer, pNewGains[iChannel], iChannel);
}
/* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */
ma_gainer_reset_smoothing_time(pGainer);
return MA_SUCCESS;
}
MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels) MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels)
......
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