diff --git a/miniaudio.h b/miniaudio.h index b8e39b4d..0dcbf6d8 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -862,12 +862,14 @@ typedef struct struct ma_channel_router { ma_channel_router_config config; - ma_bool32 isPassthrough : 1; - ma_bool32 isSimpleShuffle : 1; - ma_bool32 useSSE2 : 1; - ma_bool32 useAVX2 : 1; - ma_bool32 useAVX512 : 1; - ma_bool32 useNEON : 1; + 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]; }; @@ -29605,6 +29607,31 @@ ma_result ma_channel_router_init(const ma_channel_router_config* pConfig, ma_cha } } + /* + 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: @@ -29824,6 +29851,31 @@ void ma_channel_router__do_routing(ma_channel_router* pRouter, ma_uint64 frameCo 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) { + /* Simple case for going from stereo to mono. */ + ma_assert(pRouter->config.channelsIn == 2); + ma_assert(pRouter->config.channelsOut == 1); + + ma_uint64 iFrame; + 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. */ diff --git a/tests/ma_test_0.c b/tests/ma_test_0.c index c31a0f50..909492bf 100644 --- a/tests/ma_test_0.c +++ b/tests/ma_test_0.c @@ -1672,6 +1672,222 @@ int do_channel_routing_tests() } } + printf("Simple Mono Expansion (Mono -> Stereo)... "); + { + // The simple mono expansion case will be activated when a mono channel map is converted to anything without an LFE. + ma_channel_router_config routerConfig; + ma_zero_object(&routerConfig); + routerConfig.onReadDeinterleaved = channel_router_callback__passthrough_test; + routerConfig.pUserData = NULL; + routerConfig.mixingMode = ma_channel_mix_mode_planar_blend; + routerConfig.channelsIn = 1; + routerConfig.channelsOut = 2; + routerConfig.noSSE2 = MA_TRUE; + routerConfig.noAVX2 = MA_TRUE; + routerConfig.noAVX512 = MA_TRUE; + routerConfig.noNEON = MA_TRUE; + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, routerConfig.channelsIn, routerConfig.channelMapIn); + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, routerConfig.channelsOut, routerConfig.channelMapOut); + + ma_channel_router router; + ma_result result = ma_channel_router_init(&routerConfig, &router); + if (result == MA_SUCCESS) { + if (router.isPassthrough) { + printf("Router incorrectly configured as a passthrough.\n"); + hasError = MA_TRUE; + } + if (router.isSimpleShuffle) { + printf("Router incorrectly configured as a simple shuffle.\n"); + hasError = MA_TRUE; + } + if (!router.isSimpleMonoExpansion) { + printf("Router not configured as simple mono expansion.\n"); + hasError = MA_TRUE; + } + + // Expecting the weights to all be equal to 1 for each channel. + for (ma_uint32 iChannelIn = 0; iChannelIn < routerConfig.channelsIn; ++iChannelIn) { + for (ma_uint32 iChannelOut = 0; iChannelOut < routerConfig.channelsOut; ++iChannelOut) { + float expectedWeight = 1; + + if (router.config.weights[iChannelIn][iChannelOut] != expectedWeight) { + printf("Failed. Channel weight incorrect: %f\n", expectedWeight); + hasError = MA_TRUE; + } + } + } + } else { + printf("Failed to init router.\n"); + hasError = MA_TRUE; + } + + + // Here is where we check that the shuffle optimization works correctly. What we do is compare the output of the shuffle + // optimization with the non-shuffle output. We don't use a real sound here, but instead use values that makes it easier + // for us to check results. Each channel is given a value equal to it's index, plus 1. + float testData[MA_MAX_CHANNELS][100]; + float* ppTestData[MA_MAX_CHANNELS]; + for (ma_uint32 iChannel = 0; iChannel < routerConfig.channelsIn; ++iChannel) { + ppTestData[iChannel] = testData[iChannel]; + for (ma_uint32 iFrame = 0; iFrame < 100; ++iFrame) { + ppTestData[iChannel][iFrame] = (float)(iChannel + 1); + } + } + + routerConfig.pUserData = ppTestData; + ma_channel_router_init(&routerConfig, &router); + + float outputA[MA_MAX_CHANNELS][100]; + float outputB[MA_MAX_CHANNELS][100]; + float* ppOutputA[MA_MAX_CHANNELS]; + float* ppOutputB[MA_MAX_CHANNELS]; + for (ma_uint32 iChannel = 0; iChannel < routerConfig.channelsOut; ++iChannel) { + ppOutputA[iChannel] = outputA[iChannel]; + ppOutputB[iChannel] = outputB[iChannel]; + } + + // With optimizations. + ma_uint64 framesRead = ma_channel_router_read_deinterleaved(&router, 100, (void**)ppOutputA, router.config.pUserData); + if (framesRead != 100) { + printf("Returned frame count for optimized incorrect."); + hasError = MA_TRUE; + } + + // Without optimizations. + router.isPassthrough = MA_FALSE; + router.isSimpleShuffle = MA_FALSE; + framesRead = ma_channel_router_read_deinterleaved(&router, 100, (void**)ppOutputB, router.config.pUserData); + if (framesRead != 100) { + printf("Returned frame count for unoptimized path incorrect."); + hasError = MA_TRUE; + } + + // Compare. + for (ma_uint32 iChannel = 0; iChannel < routerConfig.channelsOut; ++iChannel) { + for (ma_uint32 iFrame = 0; iFrame < 100; ++iFrame) { + if (ppOutputA[iChannel][iFrame] != ppOutputB[iChannel][iFrame]) { + printf("Sample incorrect [%d][%d]\n", iChannel, iFrame); + hasError = MA_TRUE; + } + } + } + + + if (!hasError) { + printf("PASSED\n"); + } + } + + printf("Simple Stereo to Mono... "); + { + // The simple mono expansion case will be activated when a mono channel map is converted to anything without an LFE. + ma_channel_router_config routerConfig; + ma_zero_object(&routerConfig); + routerConfig.onReadDeinterleaved = channel_router_callback__passthrough_test; + routerConfig.pUserData = NULL; + routerConfig.mixingMode = ma_channel_mix_mode_planar_blend; + routerConfig.channelsIn = 2; + routerConfig.channelsOut = 1; + routerConfig.noSSE2 = MA_TRUE; + routerConfig.noAVX2 = MA_TRUE; + routerConfig.noAVX512 = MA_TRUE; + routerConfig.noNEON = MA_TRUE; + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, routerConfig.channelsIn, routerConfig.channelMapIn); + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, routerConfig.channelsOut, routerConfig.channelMapOut); + + ma_channel_router router; + ma_result result = ma_channel_router_init(&routerConfig, &router); + if (result == MA_SUCCESS) { + if (router.isPassthrough) { + printf("Router incorrectly configured as a passthrough.\n"); + hasError = MA_TRUE; + } + if (router.isSimpleShuffle) { + printf("Router incorrectly configured as a simple shuffle.\n"); + hasError = MA_TRUE; + } + if (router.isSimpleMonoExpansion) { + printf("Router incorrectly configured as simple mono expansion.\n"); + hasError = MA_TRUE; + } + if (!router.isStereoToMono) { + printf("Router not configured as stereo to mono.\n"); + hasError = MA_TRUE; + } + + // Expecting the weights to all be equal to 1 for each channel. + for (ma_uint32 iChannelIn = 0; iChannelIn < routerConfig.channelsIn; ++iChannelIn) { + for (ma_uint32 iChannelOut = 0; iChannelOut < routerConfig.channelsOut; ++iChannelOut) { + float expectedWeight = 0.5f; + + if (router.config.weights[iChannelIn][iChannelOut] != expectedWeight) { + printf("Failed. Channel weight incorrect: %f\n", expectedWeight); + hasError = MA_TRUE; + } + } + } + } else { + printf("Failed to init router.\n"); + hasError = MA_TRUE; + } + + + // Here is where we check that the shuffle optimization works correctly. What we do is compare the output of the shuffle + // optimization with the non-shuffle output. We don't use a real sound here, but instead use values that makes it easier + // for us to check results. Each channel is given a value equal to it's index, plus 1. + float testData[MA_MAX_CHANNELS][100]; + float* ppTestData[MA_MAX_CHANNELS]; + for (ma_uint32 iChannel = 0; iChannel < routerConfig.channelsIn; ++iChannel) { + ppTestData[iChannel] = testData[iChannel]; + for (ma_uint32 iFrame = 0; iFrame < 100; ++iFrame) { + ppTestData[iChannel][iFrame] = (float)(iChannel + 1); + } + } + + routerConfig.pUserData = ppTestData; + ma_channel_router_init(&routerConfig, &router); + + float outputA[MA_MAX_CHANNELS][100]; + float outputB[MA_MAX_CHANNELS][100]; + float* ppOutputA[MA_MAX_CHANNELS]; + float* ppOutputB[MA_MAX_CHANNELS]; + for (ma_uint32 iChannel = 0; iChannel < routerConfig.channelsOut; ++iChannel) { + ppOutputA[iChannel] = outputA[iChannel]; + ppOutputB[iChannel] = outputB[iChannel]; + } + + // With optimizations. + ma_uint64 framesRead = ma_channel_router_read_deinterleaved(&router, 100, (void**)ppOutputA, router.config.pUserData); + if (framesRead != 100) { + printf("Returned frame count for optimized incorrect."); + hasError = MA_TRUE; + } + + // Without optimizations. + router.isPassthrough = MA_FALSE; + router.isSimpleShuffle = MA_FALSE; + framesRead = ma_channel_router_read_deinterleaved(&router, 100, (void**)ppOutputB, router.config.pUserData); + if (framesRead != 100) { + printf("Returned frame count for unoptimized path incorrect."); + hasError = MA_TRUE; + } + + // Compare. + for (ma_uint32 iChannel = 0; iChannel < routerConfig.channelsOut; ++iChannel) { + for (ma_uint32 iFrame = 0; iFrame < 100; ++iFrame) { + if (ppOutputA[iChannel][iFrame] != ppOutputB[iChannel][iFrame]) { + printf("Sample incorrect [%d][%d]\n", iChannel, iFrame); + hasError = MA_TRUE; + } + } + } + + + if (!hasError) { + printf("PASSED\n"); + } + } + printf("Simple Conversion (Stereo -> 5.1)... "); { // This tests takes a Stereo to 5.1 conversion using the simple mixing mode. We should expect 0 and 1 (front/left, front/right) to have diff --git a/tests/ma_test_0.vcxproj b/tests/ma_test_0.vcxproj index e2ad5239..762b8292 100644 --- a/tests/ma_test_0.vcxproj +++ b/tests/ma_test_0.vcxproj @@ -295,12 +295,12 @@ true - false - false - false - false - false - false + true + true + true + true + true + true true @@ -383,12 +383,12 @@ true - true - true - true - true - true - true + false + false + false + false + false + false true