Create and use 2D upsampling matrices

master
Chris Robinson 2022-08-25 23:22:45 -07:00
parent 298318bd8a
commit e48c294a69
6 changed files with 185 additions and 20 deletions

View File

@ -347,9 +347,8 @@ inline uint dither_rng(uint *seed) noexcept
* at its input order, encoded back into the higher order mix, then finally
* rotated.
*/
template<size_t N>
void UpsampleBFormatTransform(size_t coeffs_order,
const std::array<std::array<float,MaxAmbiChannels>,N> &matrix1,
const al::span<const std::array<float,MaxAmbiChannels>> matrix1,
const al::span<std::array<float,MaxAmbiChannels>,MaxAmbiChannels> coeffs)
{
auto copy_coeffs = [coeffs]() noexcept
@ -906,15 +905,36 @@ 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>(Device->mAmbiOrder));
/* If the device is higher order than the voice, "upsample" the matrix */
if(Device->mAmbiOrder > voice->mAmbiOrder)
/* If the device is higher order than the voice, "upsample" the
* matrix.
*
* NOTE: Starting with second-order, a 2D upsample needs to be
* applied with a 2D source and 3D output, even when they're the
* same order. This is because higher orders have a height offset
* on various channels (i.e. when elevation=0, those height-related
* channels should be non-0).
*/
if(Device->mAmbiOrder > voice->mAmbiOrder
|| (Device->mAmbiOrder >= 2 && Is2DAmbisonic(voice->mFmtChannels)))
{
if(voice->mAmbiOrder == 1)
UpsampleBFormatTransform(Device->mAmbiOrder, AmbiScale::FirstOrderUp, shrot);
{
auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ?
AmbiScale::FirstOrder2DUp : AmbiScale::FirstOrderUp;
UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
}
else if(voice->mAmbiOrder == 2)
UpsampleBFormatTransform(Device->mAmbiOrder, AmbiScale::SecondOrderUp, shrot);
{
auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ?
AmbiScale::SecondOrder2DUp : AmbiScale::SecondOrderUp;
UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
}
else if(voice->mAmbiOrder == 3)
UpsampleBFormatTransform(Device->mAmbiOrder, AmbiScale::ThirdOrderUp, shrot);
{
auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ?
AmbiScale::ThirdOrder2DUp : AmbiScale::ThirdOrderUp;
UpsampleBFormatTransform(Device->mAmbiOrder, upsampler, shrot);
}
}
/* Convert the rotation matrix for input ordering and scaling, and

View File

@ -420,7 +420,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
if(device->mAmbiOrder > mAmbiOrder)
{
mMix = &ConvolutionState::UpsampleMix;
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder);
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, true);
(*mChans)[0].mHfScale = scales[0];
for(size_t i{1};i < mChans->size();++i)
(*mChans)[i].mHfScale = scales[1];

View File

@ -696,7 +696,7 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const Buffer&)
if(device->mAmbiOrder > 1)
{
mUpmixOutput = true;
mOrderScales = AmbiScale::GetHFOrderScales(1);
mOrderScales = AmbiScale::GetHFOrderScales(1, true);
}
else
{

View File

@ -3,11 +3,15 @@
#include "ambidefs.h"
#include "alnumbers.h"
namespace {
using AmbiChannelFloatArray = std::array<float,MaxAmbiChannels>;
constexpr auto inv_sqrt2f = static_cast<float>(1.0/al::numbers::sqrt2);
constexpr std::array<std::array<float,4>,8> FirstOrderDecoder{{
{{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }},
@ -19,7 +23,6 @@ constexpr std::array<std::array<float,4>,8> FirstOrderDecoder{{
{{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }},
{{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }},
}};
constexpr std::array<AmbiChannelFloatArray,8> FirstOrderEncoder{{
CalcAmbiCoeffs( 0.57735026919f, 0.57735026919f, 0.57735026919f),
CalcAmbiCoeffs( 0.57735026919f, 0.57735026919f, -0.57735026919f),
@ -58,6 +61,43 @@ auto CalcFirstOrderUp()
}
constexpr std::array<std::array<float,4>,4> FirstOrder2DDecoder{{
{{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f, }},
{{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f, }},
{{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f, }},
{{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f, }},
}};
constexpr std::array<AmbiChannelFloatArray,4> FirstOrder2DEncoder{{
CalcAmbiCoeffs( inv_sqrt2f, 0.0f, inv_sqrt2f),
CalcAmbiCoeffs( inv_sqrt2f, 0.0f, -inv_sqrt2f),
CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, inv_sqrt2f),
CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, -inv_sqrt2f),
}};
static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch");
/* This calculates a 2D first-order "upsampler" matrix. Same as the first-order
* matrix, just using a more optimized speaker array for horizontal-only
* content.
*/
auto CalcFirstOrder2DUp()
{
std::array<AmbiChannelFloatArray,4> res{};
for(size_t i{0};i < FirstOrder2DDecoder[0].size();++i)
{
for(size_t j{0};j < FirstOrder2DEncoder[0].size();++j)
{
double sum{0.0};
for(size_t k{0};k < FirstOrder2DDecoder.size();++k)
sum += double{FirstOrder2DDecoder[k][i]} * FirstOrder2DEncoder[k][j];
res[i][j] = static_cast<float>(sum);
}
}
return res;
}
constexpr std::array<std::array<float,9>,14> SecondOrderDecoder{{
{{ 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, 1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }},
{{ 7.142857143e-02f, 0.000000000e+00f, 0.000000000e+00f, -1.237179148e-01f, 0.000000000e+00f, 0.000000000e+00f, -7.453559925e-02f, 0.000000000e+00f, 1.290994449e-01f, }},
@ -74,7 +114,6 @@ constexpr std::array<std::array<float,9>,14> SecondOrderDecoder{{
{{ 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 7.142857143e-02f, -9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, -9.682458366e-02f, 0.000000000e+00f, }},
{{ 7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, -7.142857143e-02f, 9.682458366e-02f, 9.682458366e-02f, 0.000000000e+00f, 9.682458366e-02f, 0.000000000e+00f, }},
}};
constexpr std::array<AmbiChannelFloatArray,14> SecondOrderEncoder{{
CalcAmbiCoeffs( 0.00000000000f, 0.00000000000f, 1.00000000000f),
CalcAmbiCoeffs( 0.00000000000f, 0.00000000000f, -1.00000000000f),
@ -116,6 +155,48 @@ auto CalcSecondOrderUp()
}
constexpr std::array<std::array<float,9>,6> SecondOrder2DDecoder{{
{{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
{{ 1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }},
{{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
{{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
{{ 1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }},
{{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
}};
constexpr std::array<AmbiChannelFloatArray,6> SecondOrder2DEncoder{{
CalcAmbiCoeffs(-0.50000000000f, 0.00000000000f, 0.86602540379f),
CalcAmbiCoeffs(-1.00000000000f, 0.00000000000f, 0.00000000000f),
CalcAmbiCoeffs(-0.50000000000f, 0.00000000000f, -0.86602540379f),
CalcAmbiCoeffs( 0.50000000000f, 0.00000000000f, -0.86602540379f),
CalcAmbiCoeffs( 1.00000000000f, 0.00000000000f, 0.00000000000f),
CalcAmbiCoeffs( 0.50000000000f, 0.00000000000f, 0.86602540379f),
}};
static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(),
"Second-order 2D mismatch");
/* This calculates a 2D second-order "upsampler" matrix. Same as the second-
* order matrix, just using a more optimized speaker array for horizontal-only
* content.
*/
auto CalcSecondOrder2DUp()
{
std::array<AmbiChannelFloatArray,9> res{};
for(size_t i{0};i < SecondOrder2DDecoder[0].size();++i)
{
for(size_t j{0};j < SecondOrder2DEncoder[0].size();++j)
{
double sum{0.0};
for(size_t k{0};k < SecondOrder2DDecoder.size();++k)
sum += double{SecondOrder2DDecoder[k][i]} * SecondOrder2DEncoder[k][j];
res[i][j] = static_cast<float>(sum);
}
}
return res;
}
constexpr std::array<std::array<float,16>,20> ThirdOrderDecoder{{
{{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }},
{{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }},
@ -138,7 +219,6 @@ constexpr std::array<std::array<float,16>,20> ThirdOrderDecoder{{
{{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }},
{{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }},
}};
constexpr std::array<AmbiChannelFloatArray,20> ThirdOrderEncoder{{
CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f),
CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f),
@ -185,18 +265,75 @@ auto CalcThirdOrderUp()
return res;
}
constexpr std::array<std::array<float,16>,8> ThirdOrder2DDecoder{{
{{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }},
{{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }},
{{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }},
{{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }},
{{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }},
{{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }},
{{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }},
{{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }},
}};
constexpr std::array<AmbiChannelFloatArray,8> ThirdOrder2DEncoder{{
CalcAmbiCoeffs(-0.38268343237f, 0.00000000000f, 0.92387953251f),
CalcAmbiCoeffs(-0.92387953251f, 0.00000000000f, 0.38268343237f),
CalcAmbiCoeffs(-0.92387953251f, 0.00000000000f, -0.38268343237f),
CalcAmbiCoeffs(-0.38268343237f, 0.00000000000f, -0.92387953251f),
CalcAmbiCoeffs( 0.38268343237f, 0.00000000000f, -0.92387953251f),
CalcAmbiCoeffs( 0.92387953251f, 0.00000000000f, -0.38268343237f),
CalcAmbiCoeffs( 0.92387953251f, 0.00000000000f, 0.38268343237f),
CalcAmbiCoeffs( 0.38268343237f, 0.00000000000f, 0.92387953251f),
}};
static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-order 2D mismatch");
/* This calculates a 2D third-order "upsampler" matrix. Same as the third-order
* matrix, just using a more optimized speaker array for horizontal-only
* content.
*/
auto CalcThirdOrder2DUp()
{
std::array<AmbiChannelFloatArray,16> res{};
for(size_t i{0};i < ThirdOrder2DDecoder[0].size();++i)
{
for(size_t j{0};j < ThirdOrder2DEncoder[0].size();++j)
{
double sum{0.0};
for(size_t k{0};k < ThirdOrder2DDecoder.size();++k)
sum += double{ThirdOrder2DDecoder[k][i]} * ThirdOrder2DEncoder[k][j];
res[i][j] = static_cast<float>(sum);
}
}
return res;
}
} // namespace
const std::array<AmbiChannelFloatArray,4> AmbiScale::FirstOrderUp{CalcFirstOrderUp()};
const std::array<AmbiChannelFloatArray,4> AmbiScale::FirstOrder2DUp{CalcFirstOrder2DUp()};
const std::array<AmbiChannelFloatArray,9> AmbiScale::SecondOrderUp{CalcSecondOrderUp()};
const std::array<AmbiChannelFloatArray,9> AmbiScale::SecondOrder2DUp{CalcSecondOrder2DUp()};
const std::array<AmbiChannelFloatArray,16> AmbiScale::ThirdOrderUp{CalcThirdOrderUp()};
const std::array<AmbiChannelFloatArray,16> AmbiScale::ThirdOrder2DUp{CalcThirdOrder2DUp()};
const std::array<float,MaxAmbiOrder+1> AmbiScale::DecoderHFScale10{{
2.000000000e+00f, 1.154700538e+00f
}};
const std::array<float,MaxAmbiOrder+1> AmbiScale::DecoderHFScale1O2D{{
1.414213562e+00f, 1.000000000e+00f
}};
const std::array<float,MaxAmbiOrder+1> AmbiScale::DecoderHFScale2O{{
1.972026594e+00f, 1.527525232e+00f, 7.888106377e-01f
}};
const std::array<float,MaxAmbiOrder+1> AmbiScale::DecoderHFScale2O2D{{
1.414213562e+00f, 1.224744871e+00f, 7.071067812e-01f
}};
const std::array<float,MaxAmbiOrder+1> AmbiScale::DecoderHFScale3O{{
1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f
}};
const std::array<float,MaxAmbiOrder+1> AmbiScale::DecoderHFScale3O2D{{
1.414213562e+00f, 1.306562965e+00f, 1.000000000e+00f, 5.411961001e-01f
}};

View File

@ -114,20 +114,26 @@ struct AmbiScale {
}
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint order) noexcept
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint order, const bool is3D) noexcept
{
if(order >= 3) return DecoderHFScale3O;
if(order == 2) return DecoderHFScale2O;
return DecoderHFScale10;
if(order >= 3) return is3D ? DecoderHFScale3O : DecoderHFScale3O2D;
if(order == 2) return is3D ? DecoderHFScale2O : DecoderHFScale2O2D;
return is3D ? DecoderHFScale10 : DecoderHFScale1O2D;
}
static const std::array<float,MaxAmbiOrder+1> DecoderHFScale10;
static const std::array<float,MaxAmbiOrder+1> DecoderHFScale1O2D;
static const std::array<float,MaxAmbiOrder+1> DecoderHFScale2O;
static const std::array<float,MaxAmbiOrder+1> DecoderHFScale2O2D;
static const std::array<float,MaxAmbiOrder+1> DecoderHFScale3O;
static const std::array<float,MaxAmbiOrder+1> DecoderHFScale3O2D;
static const std::array<std::array<float,MaxAmbiChannels>,4> FirstOrderUp;
static const std::array<std::array<float,MaxAmbiChannels>,4> FirstOrder2DUp;
static const std::array<std::array<float,MaxAmbiChannels>,9> SecondOrderUp;
static const std::array<std::array<float,MaxAmbiChannels>,9> SecondOrder2DUp;
static const std::array<std::array<float,MaxAmbiChannels>,16> ThirdOrderUp;
static const std::array<std::array<float,MaxAmbiChannels>,16> ThirdOrder2DUp;
};
struct AmbiIndex {

View File

@ -899,7 +899,7 @@ void Voice::prepare(DeviceBase *device)
{
const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ?
AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()};
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder);
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, !Is2DAmbisonic(mFmtChannels));
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
for(auto &chandata : mChans)
@ -915,17 +915,19 @@ void Voice::prepare(DeviceBase *device)
* use different shelf filters after mixing it and with any old speaker
* setup the user has. To make this work, we apply the expected shelf
* filters for decoding UHJ2 to quad (only needs LF scaling), and act
* as if those 4 quad channels are encoded right back onto first-order
* B-Format, which then upsamples to higher order as normal (only needs
* HF scaling).
* as if those 4 quad channels are encoded right back onto higher-order
* B-Format.
*
* This isn't perfect, but without an entirely separate and limited
* UHJ2 path, it's better than nothing.
*/
if(mFmtChannels == FmtUHJ2)
{
mChans[0].mAmbiHFScale = 1.0f;
mChans[0].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sWLFScale;
mChans[1].mAmbiHFScale = 1.0f;
mChans[1].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sXYLFScale;
mChans[2].mAmbiHFScale = 1.0f;
mChans[2].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sXYLFScale;
}
mFlags.set(VoiceIsAmbisonic);