aboutsummaryrefslogtreecommitdiffstats
path: root/alc
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2022-08-15 08:20:20 -0700
committerChris Robinson <[email protected]>2022-08-15 09:01:03 -0700
commit6d5580805e880b7fc43d15a690b7027b010ac991 (patch)
treef324ad0454fed589ec385b10637b38e3ed42ec56 /alc
parentfbfa8a2686bab1e42bc7829fd866667fe7fd2283 (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.cpp113
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);