mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-23 08:44:04 +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);
|
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
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**************************************************************************************************************************************************************
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user