diff options
author | Chris Robinson <[email protected]> | 2022-08-15 08:20:20 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2022-08-15 09:01:03 -0700 |
commit | 6d5580805e880b7fc43d15a690b7027b010ac991 (patch) | |
tree | f324ad0454fed589ec385b10637b38e3ed42ec56 /alc | |
parent | fbfa8a2686bab1e42bc7829fd866667fe7fd2283 (diff) |
Use a decode-encode method for ambisonic upsampling
This should allow for clearer and less diffuse responses. While a cube is in
the blindspots for second-order, resulting in an identify transform for first-
to-second-order, they do start contributing to third-order channels, which
should make for sharper virtual points. The fixed HF scales should also play
nicer with larger order upmixes.
Diffstat (limited to 'alc')
-rw-r--r-- | alc/alu.cpp | 113 |
1 files changed, 107 insertions, 6 deletions
diff --git a/alc/alu.cpp b/alc/alu.cpp index bda7cf01..11bae854 100644 --- a/alc/alu.cpp +++ b/alc/alu.cpp @@ -341,6 +341,101 @@ inline uint dither_rng(uint *seed) noexcept } +/* Ambisonic upsampler functions. These are effectively matrix multiply + * operations. They take the 'coeffs' as the input matrix, and combine it with + * an "upsampler" matrix, resulting in a coefficient matrix that behaves as if + * the initial coefficients were applied to the B-Format input, which was then + * decoded to a speaker array at its input order, and finally encoded back into + * the higher order mix. + */ +void UpsampleFirstOrder(const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> coeffs) +{ + auto transpose = [coeffs]() noexcept + { + std::array<std::array<float,4>,4> res{}; + for(size_t i{0};i < 4;++i) + { + for(size_t j{0};j < 4;++j) + res[i][j] = coeffs[j][i]; + } + return res; + }; + const auto input = transpose(); + + static_assert(input[0].size() == AmbiScale::FirstOrderUp.size(), + "Mismatched first-order input and upsampler size"); + + for(size_t i{0};i < input.size();++i) + { + for(size_t j{0};j < AmbiScale::FirstOrderUp[0].size();++j) + { + double sum{0.0}; + for(size_t k{0};k < input[0].size();++k) + sum += double{input[i][k]} * AmbiScale::FirstOrderUp[k][j]; + coeffs[j][i] = static_cast<float>(sum); + } + } +} + +void UpsampleSecondOrder(const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> coeffs) +{ + auto transpose = [coeffs]() noexcept + { + std::array<std::array<float,9>,9> res{}; + for(size_t i{0};i < 9;++i) + { + for(size_t j{0};j < 9;++j) + res[i][j] = coeffs[j][i]; + } + return res; + }; + const auto input = transpose(); + + static_assert(input[0].size() == AmbiScale::SecondOrderUp.size(), + "Mismatched second-order input and upsampler size"); + + for(size_t i{0};i < input.size();++i) + { + for(size_t j{0};j < AmbiScale::SecondOrderUp[0].size();++j) + { + double sum{0.0}; + for(size_t k{0};k < input[0].size();++k) + sum += double{input[i][k]} * AmbiScale::SecondOrderUp[k][j]; + coeffs[j][i] = static_cast<float>(sum); + } + } +} + +void UpsampleThirdOrder(const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> coeffs) +{ + auto transpose = [coeffs]() noexcept + { + std::array<std::array<float,16>,16> res{}; + for(size_t i{0};i < 16;++i) + { + for(size_t j{0};j < 16;++j) + res[i][j] = coeffs[j][i]; + } + return res; + }; + const auto input = transpose(); + + static_assert(input[0].size() == AmbiScale::ThirdOrderUp.size(), + "Mismatched third-order input and upsampler size"); + + for(size_t i{0};i < input.size();++i) + { + for(size_t j{0};j < AmbiScale::ThirdOrderUp[0].size();++j) + { + double sum{0.0}; + for(size_t k{0};k < input[0].size();++k) + sum += double{input[i][k]} * AmbiScale::ThirdOrderUp[k][j]; + coeffs[j][i] = static_cast<float>(sum); + } + } +} + + inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) @@ -872,6 +967,16 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con shrot[2][1] = -U[1]; shrot[2][2] = V[1]; shrot[2][3] = N[1]; shrot[3][1] = U[2]; shrot[3][2] = -V[2]; shrot[3][3] = -N[2]; AmbiRotator(shrot, static_cast<int>(minu(voice->mAmbiOrder, Device->mAmbiOrder))); + /* If the device is higher order than the voice, "upsample" the matrix */ + if(Device->mAmbiOrder > voice->mAmbiOrder) + { + if(voice->mAmbiOrder == 1) + UpsampleFirstOrder(shrot); + else if(voice->mAmbiOrder == 2) + UpsampleSecondOrder(shrot); + else if(voice->mAmbiOrder == 3) + UpsampleThirdOrder(shrot); + } /* Convert the rotation matrix for input ordering and scaling, and * whether input is 2D or 3D. @@ -880,20 +985,16 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con GetAmbi2DLayout(voice->mAmbiLayout).data() : GetAmbiLayout(voice->mAmbiLayout).data()}; - static const uint8_t ChansPerOrder[MaxAmbiOrder+1]{1, 3, 5, 7,}; static const uint8_t OrderOffset[MaxAmbiOrder+1]{0, 1, 4, 9,}; for(size_t c{1};c < num_channels;c++) { const size_t acn{index_map[c]}; const size_t order{AmbiIndex::OrderFromChannel()[acn]}; - const size_t tocopy{ChansPerOrder[order]}; - const size_t offset{OrderOffset[order]}; const float scale{scales[acn] * coverage}; - auto in = shrot.cbegin() + offset; coeffs = std::array<float,MaxAmbiChannels>{}; - for(size_t x{0};x < tocopy;++x) - coeffs[offset+x] = in[x][acn] * scale; + for(size_t x{OrderOffset[order]};x < MaxAmbiChannels;++x) + coeffs[x] = shrot[x][acn] * scale; ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); |