Parameterize the UHJ filter length

master
Chris Robinson 2022-08-07 13:09:12 -07:00
parent b77a556d7b
commit 250f162496
7 changed files with 112 additions and 51 deletions

View File

@ -2104,8 +2104,8 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList)
}
nanoseconds::rep sample_delay{0};
if(device->mUhjEncoder)
sample_delay += UhjEncoder::sFilterDelay;
if(auto *encoder{device->mUhjEncoder.get()})
sample_delay += encoder->getDelay();
if(auto *ambidec = device->AmbiDecoder.get())
{
if(ambidec->hasStablizer())

View File

@ -1093,7 +1093,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding
if(stereomode.value_or(StereoEncoding::Default) == StereoEncoding::Uhj)
{
device->mUhjEncoder = std::make_unique<UhjEncoder>();
device->mUhjEncoder = std::make_unique<UhjEncoder<UhjLengthStd>>();
TRACE("UHJ enabled\n");
InitUhjPanning(device);
device->PostProcess = &ALCdevice::ProcessUhj;

View File

@ -187,7 +187,7 @@ struct DeviceBase {
/* Temp storage used for mixer processing. */
static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding +
UhjDecoder::sFilterDelay};
DecoderBase::sMaxDelay};
static constexpr size_t MixerChannelsMax{16};
using MixerBufferLine = std::array<float,MixerLineSize>;
alignas(16) std::array<MixerBufferLine,MixerChannelsMax> mSampleData;
@ -220,7 +220,7 @@ struct DeviceBase {
uint mIrSize{0};
/* Ambisonic-to-UHJ encoder */
std::unique_ptr<UhjEncoder> mUhjEncoder;
std::unique_ptr<UhjEncoderBase> mUhjEncoder;
/* Ambisonic decoder for speakers */
std::unique_ptr<BFormatDec> AmbiDecoder;

View File

@ -14,7 +14,15 @@
namespace {
const PhaseShifterT<UhjFilterBase::sFilterDelay*2> PShift{};
const PhaseShifterT<UhjLengthLq> PShiftLq{};
const PhaseShifterT<UhjLengthHq> PShiftHq{};
template<size_t N>
struct GetPhaseShifter;
template<>
struct GetPhaseShifter<UhjLengthLq> { static auto& Get() noexcept { return PShiftLq; } };
template<>
struct GetPhaseShifter<UhjLengthHq> { static auto& Get() noexcept { return PShiftHq; } };
} // namespace
@ -36,9 +44,12 @@ const PhaseShifterT<UhjFilterBase::sFilterDelay*2> PShift{};
* impulse with the desired shift.
*/
void UhjEncoder::encode(float *LeftOut, float *RightOut,
template<size_t N>
void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
const al::span<const float*const,3> InSamples, const size_t SamplesToDo)
{
const auto &PShift = GetPhaseShifter<N>::Get();
ASSUME(SamplesToDo > 0);
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
@ -101,9 +112,14 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut,
* where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2-
* channel excludes Q and T.
*/
void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo,
template<size_t N>
void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
const size_t forwardSamples)
{
static_assert(sFilterDelay <= sMaxDelay, "Filter delay is too large");
const auto &PShift = GetPhaseShifter<N>::Get();
ASSUME(samplesToDo > 0);
{
@ -174,9 +190,14 @@ void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo
* where j is a +90 degree phase shift. w is a variable control for the
* resulting stereo width, with the range 0 <= w <= 0.7.
*/
void UhjStereoDecoder::decode(const al::span<float*> samples, const size_t samplesToDo,
template<size_t N>
void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
const size_t forwardSamples)
{
static_assert(sFilterDelay <= sMaxDelay, "Filter delay is too large");
const auto &PShift = GetPhaseShifter<N>::Get();
ASSUME(samplesToDo > 0);
{
@ -240,3 +261,11 @@ void UhjStereoDecoder::decode(const al::span<float*> samples, const size_t sampl
for(size_t i{0};i < samplesToDo;++i)
youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i];
}
template struct UhjEncoder<UhjLengthLq>;
template struct UhjDecoder<UhjLengthLq>;
template struct UhjStereoDecoder<UhjLengthLq>;
template struct UhjEncoder<UhjLengthHq>;
template struct UhjDecoder<UhjLengthHq>;
template struct UhjStereoDecoder<UhjLengthHq>;

View File

@ -9,7 +9,55 @@
#include "resampler_limits.h"
static constexpr size_t UhjLengthLq{256};
static constexpr size_t UhjLengthHq{512};
static constexpr size_t UhjLengthStd{UhjLengthLq};
struct UhjEncoderBase {
virtual ~UhjEncoderBase() = default;
virtual size_t getDelay() noexcept = 0;
/**
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
* with an additional +3dB boost).
*/
virtual void encode(float *LeftOut, float *RightOut,
const al::span<const float*const,3> InSamples, const size_t SamplesToDo) = 0;
};
template<size_t N>
struct UhjEncoder final : public UhjEncoderBase {
static constexpr size_t sFilterDelay{N/2};
/* Delays and processing storage for the unfiltered signal. */
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
/* History for the FIR filter. */
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory{};
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
size_t getDelay() noexcept override { return sFilterDelay; }
/**
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
* with an additional +3dB boost).
*/
void encode(float *LeftOut, float *RightOut, const al::span<const float*const,3> InSamples,
const size_t SamplesToDo) override;
DEF_NEWDEL(UhjEncoder)
};
struct DecoderBase {
static constexpr size_t sMaxDelay{256};
virtual ~DecoderBase() = default;
virtual void decode(const al::span<float*> samples, const size_t samplesToDo,
@ -24,37 +72,10 @@ struct DecoderBase {
float mCurrentWidth{-1.0f};
};
template<size_t N>
struct UhjDecoder final : public DecoderBase {
static constexpr size_t sFilterDelay{N/2};
struct UhjFilterBase {
/* The filter delay is half it's effective size, so a delay of 128 has a
* FIR length of 256.
*/
static constexpr size_t sFilterDelay{128};
};
struct UhjEncoder : public UhjFilterBase {
/* Delays and processing storage for the unfiltered signal. */
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
/* History for the FIR filter. */
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory{};
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
/**
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
* with an additional +3dB boost).
*/
void encode(float *LeftOut, float *RightOut, const al::span<const float*const,3> InSamples,
const size_t SamplesToDo);
DEF_NEWDEL(UhjEncoder)
};
struct UhjDecoder : public DecoderBase, public UhjFilterBase {
/* For 2-channel UHJ, shelf filters should use these LF responses. */
static constexpr float sWLFScale{0.661f};
static constexpr float sXYLFScale{1.293f};
@ -82,7 +103,18 @@ struct UhjDecoder : public DecoderBase, public UhjFilterBase {
DEF_NEWDEL(UhjDecoder)
};
struct UhjStereoDecoder : public UhjDecoder {
template<size_t N>
struct UhjStereoDecoder final : public DecoderBase {
static constexpr size_t sFilterDelay{N/2};
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mS{};
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mD{};
alignas(16) std::array<float,sFilterDelay-1> mDTHistory{};
alignas(16) std::array<float,sFilterDelay-1> mSHistory{};
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge + sFilterDelay*2> mTemp{};
/**
* Applies Super Stereo processing on a stereo signal to create a B-Format
* signal with FuMa channel ordering and UHJ scaling. The samples span

View File

@ -854,13 +854,13 @@ void Voice::prepare(DeviceBase *device)
if(mFmtChannels == FmtSuperStereo)
{
mDecoder = std::make_unique<UhjStereoDecoder>();
mDecoderPadding = UhjStereoDecoder::sFilterDelay;
mDecoder = std::make_unique<UhjStereoDecoder<UhjLengthStd>>();
mDecoderPadding = UhjStereoDecoder<UhjLengthStd>::sFilterDelay;
}
else if(IsUHJ(mFmtChannels))
{
mDecoder = std::make_unique<UhjDecoder>();
mDecoderPadding = UhjDecoder::sFilterDelay;
mDecoder = std::make_unique<UhjDecoder<UhjLengthStd>>();
mDecoderPadding = UhjDecoder<UhjLengthStd>::sFilterDelay;
}
else
{
@ -908,9 +908,9 @@ void Voice::prepare(DeviceBase *device)
*/
if(mFmtChannels == FmtUHJ2)
{
mChans[0].mAmbiLFScale = UhjDecoder::sWLFScale;
mChans[1].mAmbiLFScale = UhjDecoder::sXYLFScale;
mChans[2].mAmbiLFScale = UhjDecoder::sXYLFScale;
mChans[0].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sWLFScale;
mChans[1].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sXYLFScale;
mChans[2].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sXYLFScale;
}
mFlags.set(VoiceIsAmbisonic);
}
@ -930,9 +930,9 @@ void Voice::prepare(DeviceBase *device)
chandata.mDryParams.NFCtrlFilter = device->mNFCtrlFilter;
std::fill_n(chandata.mWetParams.begin(), device->NumAuxSends, SendParams{});
}
mChans[0].mAmbiLFScale = UhjDecoder::sWLFScale;
mChans[1].mAmbiLFScale = UhjDecoder::sXYLFScale;
mChans[2].mAmbiLFScale = UhjDecoder::sXYLFScale;
mChans[0].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sWLFScale;
mChans[1].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sXYLFScale;
mChans[2].mAmbiLFScale = UhjDecoder<UhjLengthStd>::sXYLFScale;
mFlags.set(VoiceIsAmbisonic);
}
else

View File

@ -51,7 +51,7 @@ enum class DirectMode : unsigned char {
/* Maximum number of extra source samples that may need to be loaded, for
* resampling or conversion purposes.
*/
constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay};
constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + DecoderBase::sMaxDelay};
enum {