mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
API CHANGE: Remove ma_channel_router.
This has been replaced with ma_channel_converter.
This commit is contained in:
-775
@@ -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
|
||||
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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user