Commit dbaa2479 authored by David Reid's avatar David Reid

API CHANGE: Remove ma_channel_router.

This has been replaced with ma_channel_converter.
parent d594b07e
...@@ -1446,39 +1446,6 @@ struct ma_format_converter ...@@ -1446,39 +1446,6 @@ struct ma_format_converter
typedef struct ma_channel_router ma_channel_router;
typedef ma_uint32 (* ma_channel_router_read_deinterleaved_proc)(ma_channel_router* pRouter, ma_uint32 frameCount, void** ppSamplesOut, void* pUserData);
typedef struct
{
ma_uint32 channelsIn;
ma_uint32 channelsOut;
ma_channel channelMapIn[MA_MAX_CHANNELS];
ma_channel channelMapOut[MA_MAX_CHANNELS];
ma_channel_mix_mode mixingMode;
float weights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */
ma_bool32 noSSE2 : 1;
ma_bool32 noAVX2 : 1;
ma_bool32 noAVX512 : 1;
ma_bool32 noNEON : 1;
ma_channel_router_read_deinterleaved_proc onReadDeinterleaved;
void* pUserData;
} ma_channel_router_config;
struct ma_channel_router
{
ma_channel_router_config config;
ma_bool32 isPassthrough : 1;
ma_bool32 isSimpleShuffle : 1;
ma_bool32 isSimpleMonoExpansion : 1;
ma_bool32 isStereoToMono : 1;
ma_bool32 useSSE2 : 1;
ma_bool32 useAVX2 : 1;
ma_bool32 useAVX512 : 1;
ma_bool32 useNEON : 1;
ma_uint8 shuffleTable[MA_MAX_CHANNELS];
};
/************************************************************************************************************************************************************ /************************************************************************************************************************************************************
************************************************************************************************************************************************************* *************************************************************************************************************************************************************
...@@ -1655,88 +1622,6 @@ ma_format_converter_config ma_format_converter_config_init(ma_format formatIn, m ...@@ -1655,88 +1622,6 @@ ma_format_converter_config ma_format_converter_config_init(ma_format formatIn, m
ma_format_converter_config ma_format_converter_config_init_deinterleaved(ma_format formatIn, ma_format formatOut, ma_uint32 channels, ma_format_converter_read_deinterleaved_proc onReadDeinterleaved, void* pUserData); ma_format_converter_config ma_format_converter_config_init_deinterleaved(ma_format formatIn, ma_format formatOut, ma_uint32 channels, ma_format_converter_read_deinterleaved_proc onReadDeinterleaved, void* pUserData);
/************************************************************************************************************************************************************
Channel Routing
===============
There are two main things you can do with the channel router:
1) Rearrange channels
2) Convert from one channel count to another
Channel Rearrangement
---------------------
A simple example of channel rearrangement may be swapping the left and right channels in a stereo stream. To do this you just pass in the same channel
count for both the input and output with channel maps that contain the same channels (in a different order).
Channel Conversion
------------------
The channel router can also convert from one channel count to another, such as converting a 5.1 stream to stero. When changing the channel count, the
router will first perform a 1:1 mapping of channel positions that are present in both the input and output channel maps. The second thing it will do
is distribute the input mono channel (if any) across all output channels, excluding any None and LFE channels. If there is an output mono channel, all
input channels will be averaged, excluding any None and LFE channels.
The last case to consider is when a channel position in the input channel map is not present in the output channel map, and vice versa. In this case the
channel router will perform a blend of other related channels to produce an audible channel. There are several blending modes.
1) Simple
Unmatched channels are silenced.
2) Planar Blending
Channels are blended based on a set of planes that each speaker emits audio from.
Rectangular / Planar Blending
-----------------------------
In this mode, channel positions are associated with a set of planes where the channel conceptually emits audio from. An example is the front/left speaker.
This speaker is positioned to the front of the listener, so you can think of it as emitting audio from the front plane. It is also positioned to the left
of the listener so you can think of it as also emitting audio from the left plane. Now consider the (unrealistic) situation where the input channel map
contains only the front/left channel position, but the output channel map contains both the front/left and front/center channel. When deciding on the audio
data to send to the front/center speaker (which has no 1:1 mapping with an input channel) we need to use some logic based on our available input channel
positions.
As mentioned earlier, our front/left speaker is, conceptually speaking, emitting audio from the front _and_ the left planes. Similarly, the front/center
speaker is emitting audio from _only_ the front plane. What these two channels have in common is that they are both emitting audio from the front plane.
Thus, it makes sense that the front/center speaker should receive some contribution from the front/left channel. How much contribution depends on their
planar relationship (thus the name of this blending technique).
Because the front/left channel is emitting audio from two planes (front and left), you can think of it as though it's willing to dedicate 50% of it's total
volume to each of it's planes (a channel position emitting from 1 plane would be willing to given 100% of it's total volume to that plane, and a channel
position emitting from 3 planes would be willing to given 33% of it's total volume to each plane). Similarly, the front/center speaker is emitting audio
from only one plane so you can think of it as though it's willing to _take_ 100% of it's volume from front plane emissions. Now, since the front/left
channel is willing to _give_ 50% of it's total volume to the front plane, and the front/center speaker is willing to _take_ 100% of it's total volume
from the front, you can imagine that 50% of the front/left speaker will be given to the front/center speaker.
Usage
-----
To use the channel router you need to specify three things:
1) The input channel count and channel map
2) The output channel count and channel map
3) The mixing mode to use in the case where a 1:1 mapping is unavailable
Note that input and output data is always deinterleaved 32-bit floating point.
Initialize the channel router with ma_channel_router_init(). You will need to pass in a config object which specifies the input and output configuration,
mixing mode and a callback for sending data to the router. This callback will be called when input data needs to be sent to the router for processing. Note
that the mixing mode is only used when a 1:1 mapping is unavailable. This includes the custom weights mode.
Read data from the channel router with ma_channel_router_read_deinterleaved(). Output data is always 32-bit floating point.
************************************************************************************************************************************************************/
/*
Initializes a channel router where it is assumed that the input data is non-interleaved.
*/
ma_result ma_channel_router_init(const ma_channel_router_config* pConfig, ma_channel_router* pRouter);
/*
Reads data from the channel router as deinterleaved channels.
*/
ma_uint64 ma_channel_router_read_deinterleaved(ma_channel_router* pRouter, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData);
/*
Helper for initializing a channel router config.
*/
ma_channel_router_config ma_channel_router_config_init(ma_uint32 channelsIn, const ma_channel channelMapIn[MA_MAX_CHANNELS], ma_uint32 channelsOut, const ma_channel channelMapOut[MA_MAX_CHANNELS], ma_channel_mix_mode mixingMode, ma_channel_router_read_deinterleaved_proc onRead, void* pUserData);
/************************************************************************************************************************************************************ /************************************************************************************************************************************************************
Conversion Conversion
...@@ -34149,666 +34034,6 @@ ma_format_converter_config ma_format_converter_config_init_deinterleaved(ma_form ...@@ -34149,666 +34034,6 @@ ma_format_converter_config ma_format_converter_config_init_deinterleaved(ma_form
/**************************************************************************************************************************************************************
Channel Routing
**************************************************************************************************************************************************************/
/*
-X = Left, +X = Right
-Y = Bottom, +Y = Top
-Z = Front, +Z = Back
*/
typedef struct
{
float x;
float y;
float z;
} ma_vec3;
static MA_INLINE ma_vec3 ma_vec3f(float x, float y, float z)
{
ma_vec3 r;
r.x = x;
r.y = y;
r.z = z;
return r;
}
static MA_INLINE ma_vec3 ma_vec3_add(ma_vec3 a, ma_vec3 b)
{
return ma_vec3f(
a.x + b.x,
a.y + b.y,
a.z + b.z
);
}
static MA_INLINE ma_vec3 ma_vec3_sub(ma_vec3 a, ma_vec3 b)
{
return ma_vec3f(
a.x - b.x,
a.y - b.y,
a.z - b.z
);
}
static MA_INLINE ma_vec3 ma_vec3_mul(ma_vec3 a, ma_vec3 b)
{
return ma_vec3f(
a.x * b.x,
a.y * b.y,
a.z * b.z
);
}
static MA_INLINE ma_vec3 ma_vec3_div(ma_vec3 a, ma_vec3 b)
{
return ma_vec3f(
a.x / b.x,
a.y / b.y,
a.z / b.z
);
}
static MA_INLINE float ma_vec3_dot(ma_vec3 a, ma_vec3 b)
{
return a.x*b.x + a.y*b.y + a.z*b.z;
}
static MA_INLINE float ma_vec3_length2(ma_vec3 a)
{
return ma_vec3_dot(a, a);
}
static MA_INLINE float ma_vec3_length(ma_vec3 a)
{
return (float)sqrt(ma_vec3_length2(a));
}
static MA_INLINE ma_vec3 ma_vec3_normalize(ma_vec3 a)
{
float len = 1 / ma_vec3_length(a);
ma_vec3 r;
r.x = a.x * len;
r.y = a.y * len;
r.z = a.z * len;
return r;
}
static MA_INLINE float ma_vec3_distance(ma_vec3 a, ma_vec3 b)
{
return ma_vec3_length(ma_vec3_sub(a, b));
}
float ma_channel_router__calculate_input_channel_planar_weight(const ma_channel_router* pRouter, ma_channel channelPositionIn, ma_channel channelPositionOut)
{
ma_assert(pRouter != NULL);
(void)pRouter;
return ma_calculate_channel_position_rectangular_weight(channelPositionIn, channelPositionOut);
}
ma_bool32 ma_channel_router__is_spatial_channel_position(const ma_channel_router* pRouter, ma_channel channelPosition)
{
int i;
ma_assert(pRouter != NULL);
(void)pRouter;
if (channelPosition == MA_CHANNEL_NONE || channelPosition == MA_CHANNEL_MONO || channelPosition == MA_CHANNEL_LFE) {
return MA_FALSE;
}
for (i = 0; i < 6; ++i) {
if (g_maChannelPlaneRatios[channelPosition][i] != 0) {
return MA_TRUE;
}
}
return MA_FALSE;
}
ma_result ma_channel_router_init(const ma_channel_router_config* pConfig, ma_channel_router* pRouter)
{
ma_uint32 iChannelIn;
ma_uint32 iChannelOut;
if (pRouter == NULL) {
return MA_INVALID_ARGS;
}
ma_zero_object(pRouter);
if (pConfig == NULL) {
return MA_INVALID_ARGS;
}
if (pConfig->onReadDeinterleaved == NULL) {
return MA_INVALID_ARGS;
}
if (!ma_channel_map_valid(pConfig->channelsIn, pConfig->channelMapIn)) {
return MA_INVALID_ARGS; /* Invalid input channel map. */
}
if (!ma_channel_map_valid(pConfig->channelsOut, pConfig->channelMapOut)) {
return MA_INVALID_ARGS; /* Invalid output channel map. */
}
pRouter->config = *pConfig;
/* SIMD */
pRouter->useSSE2 = ma_has_sse2() && !pConfig->noSSE2;
pRouter->useAVX2 = ma_has_avx2() && !pConfig->noAVX2;
pRouter->useAVX512 = ma_has_avx512f() && !pConfig->noAVX512;
pRouter->useNEON = ma_has_neon() && !pConfig->noNEON;
/* If the input and output channels and channel maps are the same we should use a passthrough. */
if (pRouter->config.channelsIn == pRouter->config.channelsOut) {
if (ma_channel_map_equal(pRouter->config.channelsIn, pRouter->config.channelMapIn, pRouter->config.channelMapOut)) {
pRouter->isPassthrough = MA_TRUE;
}
if (ma_channel_map_blank(pRouter->config.channelsIn, pRouter->config.channelMapIn) || ma_channel_map_blank(pRouter->config.channelsOut, pRouter->config.channelMapOut)) {
pRouter->isPassthrough = MA_TRUE;
}
}
/*
We can use a simple case for expanding the mono channel. This will when expanding a mono input into any output so long
as no LFE is present in the output.
*/
if (!pRouter->isPassthrough) {
if (pRouter->config.channelsIn == 1 && pRouter->config.channelMapIn[0] == MA_CHANNEL_MONO) {
/* Optimal case if no LFE is in the output channel map. */
pRouter->isSimpleMonoExpansion = MA_TRUE;
if (ma_channel_map_contains_channel_position(pRouter->config.channelsOut, pRouter->config.channelMapOut, MA_CHANNEL_LFE)) {
pRouter->isSimpleMonoExpansion = MA_FALSE;
}
}
}
/* Another optimized case is stereo to mono. */
if (!pRouter->isPassthrough) {
if (pRouter->config.channelsOut == 1 && pRouter->config.channelMapOut[0] == MA_CHANNEL_MONO && pRouter->config.channelsIn == 2) {
/* Optimal case if no LFE is in the input channel map. */
pRouter->isStereoToMono = MA_TRUE;
if (ma_channel_map_contains_channel_position(pRouter->config.channelsIn, pRouter->config.channelMapIn, MA_CHANNEL_LFE)) {
pRouter->isStereoToMono = MA_FALSE;
}
}
}
/*
Here is where we do a bit of pre-processing to know how each channel should be combined to make up the output. Rules:
1) If it's a passthrough, do nothing - it's just a simple memcpy().
2) If the channel counts are the same and every channel position in the input map is present in the output map, use a
simple shuffle. An example might be different 5.1 channel layouts.
3) Otherwise channels are blended based on spatial locality.
*/
if (!pRouter->isPassthrough) {
if (pRouter->config.channelsIn == pRouter->config.channelsOut) {
ma_bool32 areAllChannelPositionsPresent = MA_TRUE;
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_bool32 isInputChannelPositionInOutput = MA_FALSE;
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
if (pRouter->config.channelMapIn[iChannelIn] == pRouter->config.channelMapOut[iChannelOut]) {
isInputChannelPositionInOutput = MA_TRUE;
break;
}
}
if (!isInputChannelPositionInOutput) {
areAllChannelPositionsPresent = MA_FALSE;
break;
}
}
if (areAllChannelPositionsPresent) {
pRouter->isSimpleShuffle = MA_TRUE;
/*
All the router will be doing is rearranging channels which means all we need to do is use a shuffling table which is just
a mapping between the index of the input channel to the index of the output channel.
*/
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
if (pRouter->config.channelMapIn[iChannelIn] == pRouter->config.channelMapOut[iChannelOut]) {
pRouter->shuffleTable[iChannelIn] = (ma_uint8)iChannelOut;
break;
}
}
}
}
}
}
/*
Here is where weights are calculated. Note that we calculate the weights at all times, even when using a passthrough and simple
shuffling. We use different algorithms for calculating weights depending on our mixing mode.
In simple mode we don't do any blending (except for converting between mono, which is done in a later step). Instead we just
map 1:1 matching channels. In this mode, if no channels in the input channel map correspond to anything in the output channel
map, nothing will be heard!
*/
/* In all cases we need to make sure all channels that are present in both channel maps have a 1:1 mapping. */
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn];
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut];
if (channelPosIn == channelPosOut) {
pRouter->config.weights[iChannelIn][iChannelOut] = 1;
}
}
}
/*
The mono channel is accumulated on all other channels, except LFE. Make sure in this loop we exclude output mono channels since
they were handled in the pass above.
*/
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn];
if (channelPosIn == MA_CHANNEL_MONO) {
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut];
if (channelPosOut != MA_CHANNEL_NONE && channelPosOut != MA_CHANNEL_MONO && channelPosOut != MA_CHANNEL_LFE) {
pRouter->config.weights[iChannelIn][iChannelOut] = 1;
}
}
}
}
/* The output mono channel is the average of all non-none, non-mono and non-lfe input channels. */
{
ma_uint32 len = 0;
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn];
if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) {
len += 1;
}
}
if (len > 0) {
float monoWeight = 1.0f / len;
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut];
if (channelPosOut == MA_CHANNEL_MONO) {
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn];
if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) {
pRouter->config.weights[iChannelIn][iChannelOut] += monoWeight;
}
}
}
}
}
}
/* Input and output channels that are not present on the other side need to be blended in based on spatial locality. */
switch (pRouter->config.mixingMode)
{
case ma_channel_mix_mode_rectangular:
{
/* Unmapped input channels. */
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn];
if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosIn)) {
if (!ma_channel_map_contains_channel_position(pRouter->config.channelsOut, pRouter->config.channelMapOut, channelPosIn)) {
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut];
if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosOut)) {
float weight = 0;
if (pRouter->config.mixingMode == ma_channel_mix_mode_planar_blend) {
weight = ma_channel_router__calculate_input_channel_planar_weight(pRouter, channelPosIn, channelPosOut);
}
/* Only apply the weight if we haven't already got some contribution from the respective channels. */
if (pRouter->config.weights[iChannelIn][iChannelOut] == 0) {
pRouter->config.weights[iChannelIn][iChannelOut] = weight;
}
}
}
}
}
}
/* Unmapped output channels. */
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_channel channelPosOut = pRouter->config.channelMapOut[iChannelOut];
if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosOut)) {
if (!ma_channel_map_contains_channel_position(pRouter->config.channelsIn, pRouter->config.channelMapIn, channelPosOut)) {
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
ma_channel channelPosIn = pRouter->config.channelMapIn[iChannelIn];
if (ma_channel_router__is_spatial_channel_position(pRouter, channelPosIn)) {
float weight = 0;
if (pRouter->config.mixingMode == ma_channel_mix_mode_planar_blend) {
weight = ma_channel_router__calculate_input_channel_planar_weight(pRouter, channelPosIn, channelPosOut);
}
/* Only apply the weight if we haven't already got some contribution from the respective channels. */
if (pRouter->config.weights[iChannelIn][iChannelOut] == 0) {
pRouter->config.weights[iChannelIn][iChannelOut] = weight;
}
}
}
}
}
}
} break;
case ma_channel_mix_mode_custom_weights:
case ma_channel_mix_mode_simple:
default:
{
/* Fallthrough. */
} break;
}
return MA_SUCCESS;
}
static MA_INLINE ma_bool32 ma_channel_router__can_use_sse2(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn)
{
return pRouter->useSSE2 && (((ma_uintptr)pSamplesOut & 15) == 0) && (((ma_uintptr)pSamplesIn & 15) == 0);
}
static MA_INLINE ma_bool32 ma_channel_router__can_use_avx2(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn)
{
return pRouter->useAVX2 && (((ma_uintptr)pSamplesOut & 31) == 0) && (((ma_uintptr)pSamplesIn & 31) == 0);
}
static MA_INLINE ma_bool32 ma_channel_router__can_use_avx512(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn)
{
return pRouter->useAVX512 && (((ma_uintptr)pSamplesOut & 63) == 0) && (((ma_uintptr)pSamplesIn & 63) == 0);
}
static MA_INLINE ma_bool32 ma_channel_router__can_use_neon(ma_channel_router* pRouter, const float* pSamplesOut, const float* pSamplesIn)
{
return pRouter->useNEON && (((ma_uintptr)pSamplesOut & 15) == 0) && (((ma_uintptr)pSamplesIn & 15) == 0);
}
void ma_channel_router__do_routing(ma_channel_router* pRouter, ma_uint64 frameCount, float** ppSamplesOut, const float** ppSamplesIn)
{
ma_uint32 iChannelIn;
ma_uint32 iChannelOut;
ma_assert(pRouter != NULL);
ma_assert(pRouter->isPassthrough == MA_FALSE);
if (pRouter->isSimpleShuffle) {
/* A shuffle is just a re-arrangement of channels and does not require any arithmetic. */
ma_assert(pRouter->config.channelsIn == pRouter->config.channelsOut);
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
iChannelOut = pRouter->shuffleTable[iChannelIn];
ma_copy_memory_64(ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn], frameCount * sizeof(float));
}
} else if (pRouter->isSimpleMonoExpansion) {
/* Simple case for expanding from mono. */
if (pRouter->config.channelsOut == 2) {
ma_uint64 iFrame;
for (iFrame = 0; iFrame < frameCount; ++iFrame) {
ppSamplesOut[0][iFrame] = ppSamplesIn[0][iFrame];
ppSamplesOut[1][iFrame] = ppSamplesIn[0][iFrame];
}
} else {
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_uint64 iFrame;
for (iFrame = 0; iFrame < frameCount; ++iFrame) {
ppSamplesOut[iChannelOut][iFrame] = ppSamplesIn[0][iFrame];
}
}
}
} else if (pRouter->isStereoToMono) {
ma_uint64 iFrame;
/* Simple case for going from stereo to mono. */
ma_assert(pRouter->config.channelsIn == 2);
ma_assert(pRouter->config.channelsOut == 1);
for (iFrame = 0; iFrame < frameCount; ++iFrame) {
ppSamplesOut[0][iFrame] = (ppSamplesIn[0][iFrame] + ppSamplesIn[1][iFrame]) * 0.5f;
}
} else {
/* This is the more complicated case. Each of the output channels is accumulated with 0 or more input channels. */
/* Clear. */
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_zero_memory_64(ppSamplesOut[iChannelOut], frameCount * sizeof(float));
}
/* Accumulate. */
for (iChannelIn = 0; iChannelIn < pRouter->config.channelsIn; ++iChannelIn) {
for (iChannelOut = 0; iChannelOut < pRouter->config.channelsOut; ++iChannelOut) {
ma_uint64 iFrame = 0;
#if defined(MA_SUPPORT_NEON)
if (ma_channel_router__can_use_neon(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) {
float32x4_t weight = vmovq_n_f32(pRouter->config.weights[iChannelIn][iChannelOut]);
ma_uint64 frameCount4 = frameCount/4;
ma_uint64 iFrame4;
for (iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) {
float32x4_t* pO = (float32x4_t*)ppSamplesOut[iChannelOut] + iFrame4;
float32x4_t* pI = (float32x4_t*)ppSamplesIn [iChannelIn ] + iFrame4;
*pO = vaddq_f32(*pO, vmulq_f32(*pI, weight));
}
iFrame += frameCount4*4;
}
else
#endif
#if defined(MA_SUPPORT_AVX512)
if (ma_channel_router__can_use_avx512(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) {
__m512 weight = _mm512_set1_ps(pRouter->config.weights[iChannelIn][iChannelOut]);
ma_uint64 frameCount16 = frameCount/16;
ma_uint64 iFrame16;
for (iFrame16 = 0; iFrame16 < frameCount16; iFrame16 += 1) {
__m512* pO = (__m512*)ppSamplesOut[iChannelOut] + iFrame16;
__m512* pI = (__m512*)ppSamplesIn [iChannelIn ] + iFrame16;
*pO = _mm512_add_ps(*pO, _mm512_mul_ps(*pI, weight));
}
iFrame += frameCount16*16;
}
else
#endif
#if defined(MA_SUPPORT_AVX2)
if (ma_channel_router__can_use_avx2(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) {
__m256 weight = _mm256_set1_ps(pRouter->config.weights[iChannelIn][iChannelOut]);
ma_uint64 frameCount8 = frameCount/8;
ma_uint64 iFrame8;
for (iFrame8 = 0; iFrame8 < frameCount8; iFrame8 += 1) {
__m256* pO = (__m256*)ppSamplesOut[iChannelOut] + iFrame8;
__m256* pI = (__m256*)ppSamplesIn [iChannelIn ] + iFrame8;
*pO = _mm256_add_ps(*pO, _mm256_mul_ps(*pI, weight));
}
iFrame += frameCount8*8;
}
else
#endif
#if defined(MA_SUPPORT_SSE2)
if (ma_channel_router__can_use_sse2(pRouter, ppSamplesOut[iChannelOut], ppSamplesIn[iChannelIn])) {
__m128 weight = _mm_set1_ps(pRouter->config.weights[iChannelIn][iChannelOut]);
ma_uint64 frameCount4 = frameCount/4;
ma_uint64 iFrame4;
for (iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) {
__m128* pO = (__m128*)ppSamplesOut[iChannelOut] + iFrame4;
__m128* pI = (__m128*)ppSamplesIn [iChannelIn ] + iFrame4;
*pO = _mm_add_ps(*pO, _mm_mul_ps(*pI, weight));
}
iFrame += frameCount4*4;
} else
#endif
{ /* Reference. */
float weight0 = pRouter->config.weights[iChannelIn][iChannelOut];
float weight1 = pRouter->config.weights[iChannelIn][iChannelOut];
float weight2 = pRouter->config.weights[iChannelIn][iChannelOut];
float weight3 = pRouter->config.weights[iChannelIn][iChannelOut];
ma_uint64 frameCount4 = frameCount/4;
ma_uint64 iFrame4;
for (iFrame4 = 0; iFrame4 < frameCount4; iFrame4 += 1) {
ppSamplesOut[iChannelOut][iFrame+0] += ppSamplesIn[iChannelIn][iFrame+0] * weight0;
ppSamplesOut[iChannelOut][iFrame+1] += ppSamplesIn[iChannelIn][iFrame+1] * weight1;
ppSamplesOut[iChannelOut][iFrame+2] += ppSamplesIn[iChannelIn][iFrame+2] * weight2;
ppSamplesOut[iChannelOut][iFrame+3] += ppSamplesIn[iChannelIn][iFrame+3] * weight3;
iFrame += 4;
}
}
/* Leftover. */
for (; iFrame < frameCount; ++iFrame) {
ppSamplesOut[iChannelOut][iFrame] += ppSamplesIn[iChannelIn][iFrame] * pRouter->config.weights[iChannelIn][iChannelOut];
}
}
}
}
}
ma_uint64 ma_channel_router_read_deinterleaved(ma_channel_router* pRouter, ma_uint64 frameCount, void** ppSamplesOut, void* pUserData)
{
if (pRouter == NULL || ppSamplesOut == NULL) {
return 0;
}
/* Fast path for a passthrough. */
if (pRouter->isPassthrough) {
if (frameCount <= 0xFFFFFFFF) {
return (ma_uint32)pRouter->config.onReadDeinterleaved(pRouter, (ma_uint32)frameCount, ppSamplesOut, pUserData);
} else {
float* ppNextSamplesOut[MA_MAX_CHANNELS];
ma_uint64 totalFramesRead;
ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(float*) * pRouter->config.channelsOut);
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
ma_uint32 iChannel;
ma_uint32 framesJustRead;
ma_uint64 framesRemaining = (frameCount - totalFramesRead);
ma_uint64 framesToReadRightNow = framesRemaining;
if (framesToReadRightNow > 0xFFFFFFFF) {
framesToReadRightNow = 0xFFFFFFFF;
}
framesJustRead = (ma_uint32)pRouter->config.onReadDeinterleaved(pRouter, (ma_uint32)framesToReadRightNow, (void**)ppNextSamplesOut, pUserData);
if (framesJustRead == 0) {
break;
}
totalFramesRead += framesJustRead;
if (framesJustRead < framesToReadRightNow) {
break;
}
for (iChannel = 0; iChannel < pRouter->config.channelsOut; ++iChannel) {
ppNextSamplesOut[iChannel] += framesJustRead;
}
}
return totalFramesRead;
}
}
/* Slower path for a non-passthrough. */
{
float* ppNextSamplesOut[MA_MAX_CHANNELS];
float* ppTemp[MA_MAX_CHANNELS];
size_t maxBytesToReadPerFrameEachIteration;
size_t maxFramesToReadEachIteration;
ma_uint64 totalFramesRead;
MA_ALIGN(MA_SIMD_ALIGNMENT) float temp[MA_MAX_CHANNELS * 256];
ma_assert(sizeof(temp) <= 0xFFFFFFFF);
ma_copy_memory(ppNextSamplesOut, ppSamplesOut, sizeof(float*) * pRouter->config.channelsOut);
ma_split_buffer(temp, sizeof(temp), pRouter->config.channelsIn, MA_SIMD_ALIGNMENT, (void**)&ppTemp, &maxBytesToReadPerFrameEachIteration);
maxFramesToReadEachIteration = maxBytesToReadPerFrameEachIteration/sizeof(float);
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
ma_uint32 iChannel;
ma_uint32 framesJustRead;
ma_uint64 framesRemaining = (frameCount - totalFramesRead);
ma_uint64 framesToReadRightNow = framesRemaining;
if (framesToReadRightNow > maxFramesToReadEachIteration) {
framesToReadRightNow = maxFramesToReadEachIteration;
}
framesJustRead = pRouter->config.onReadDeinterleaved(pRouter, (ma_uint32)framesToReadRightNow, (void**)ppTemp, pUserData);
if (framesJustRead == 0) {
break;
}
ma_channel_router__do_routing(pRouter, framesJustRead, (float**)ppNextSamplesOut, (const float**)ppTemp); /* <-- Real work is done here. */
totalFramesRead += framesJustRead;
if (totalFramesRead < frameCount) {
for (iChannel = 0; iChannel < pRouter->config.channelsIn; iChannel += 1) {
ppNextSamplesOut[iChannel] += framesJustRead;
}
}
if (framesJustRead < framesToReadRightNow) {
break;
}
}
return totalFramesRead;
}
}
ma_channel_router_config ma_channel_router_config_init(ma_uint32 channelsIn, const ma_channel channelMapIn[MA_MAX_CHANNELS], ma_uint32 channelsOut, const ma_channel channelMapOut[MA_MAX_CHANNELS], ma_channel_mix_mode mixingMode, ma_channel_router_read_deinterleaved_proc onRead, void* pUserData)
{
ma_channel_router_config config;
ma_uint32 iChannel;
ma_zero_object(&config);
config.channelsIn = channelsIn;
for (iChannel = 0; iChannel < channelsIn; ++iChannel) {
config.channelMapIn[iChannel] = channelMapIn[iChannel];
}
config.channelsOut = channelsOut;
for (iChannel = 0; iChannel < channelsOut; ++iChannel) {
config.channelMapOut[iChannel] = channelMapOut[iChannel];
}
config.mixingMode = mixingMode;
config.onReadDeinterleaved = onRead;
config.pUserData = pUserData;
return config;
}
/************************************************************************************************************************************************************** /**************************************************************************************************************************************************************
Format Conversion Format Conversion
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