From 250f1624964c2c53e00d18fd1ec2bbc77c860298 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 7 Aug 2022 13:09:12 -0700 Subject: Parameterize the UHJ filter length --- alc/alc.cpp | 4 +-- alc/panning.cpp | 2 +- core/device.h | 4 +-- core/uhjfilter.cpp | 37 +++++++++++++++++++++++++--- core/uhjfilter.h | 72 +++++++++++++++++++++++++++++++++++++++--------------- core/voice.cpp | 20 +++++++-------- core/voice.h | 2 +- 7 files changed, 101 insertions(+), 40 deletions(-) diff --git a/alc/alc.cpp b/alc/alc.cpp index f9834197..d17969f4 100644 --- a/alc/alc.cpp +++ b/alc/alc.cpp @@ -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()) diff --git a/alc/panning.cpp b/alc/panning.cpp index 113ea280..22688bc9 100644 --- a/alc/panning.cpp +++ b/alc/panning.cpp @@ -1093,7 +1093,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmUhjEncoder = std::make_unique(); + device->mUhjEncoder = std::make_unique>(); TRACE("UHJ enabled\n"); InitUhjPanning(device); device->PostProcess = &ALCdevice::ProcessUhj; diff --git a/core/device.h b/core/device.h index e52d015f..bf995e5b 100644 --- a/core/device.h +++ b/core/device.h @@ -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; alignas(16) std::array mSampleData; @@ -220,7 +220,7 @@ struct DeviceBase { uint mIrSize{0}; /* Ambisonic-to-UHJ encoder */ - std::unique_ptr mUhjEncoder; + std::unique_ptr mUhjEncoder; /* Ambisonic decoder for speakers */ std::unique_ptr AmbiDecoder; diff --git a/core/uhjfilter.cpp b/core/uhjfilter.cpp index 52e7f964..b327a627 100644 --- a/core/uhjfilter.cpp +++ b/core/uhjfilter.cpp @@ -14,7 +14,15 @@ namespace { -const PhaseShifterT PShift{}; +const PhaseShifterT PShiftLq{}; +const PhaseShifterT PShiftHq{}; + +template +struct GetPhaseShifter; +template<> +struct GetPhaseShifter { static auto& Get() noexcept { return PShiftLq; } }; +template<> +struct GetPhaseShifter { static auto& Get() noexcept { return PShiftHq; } }; } // namespace @@ -36,9 +44,12 @@ const PhaseShifterT PShift{}; * impulse with the desired shift. */ -void UhjEncoder::encode(float *LeftOut, float *RightOut, +template +void UhjEncoder::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { + const auto &PShift = GetPhaseShifter::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 samples, const size_t samplesToDo, +template +void UhjDecoder::decode(const al::span samples, const size_t samplesToDo, const size_t forwardSamples) { + static_assert(sFilterDelay <= sMaxDelay, "Filter delay is too large"); + + const auto &PShift = GetPhaseShifter::Get(); + ASSUME(samplesToDo > 0); { @@ -174,9 +190,14 @@ void UhjDecoder::decode(const al::span 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 samples, const size_t samplesToDo, +template +void UhjStereoDecoder::decode(const al::span samples, const size_t samplesToDo, const size_t forwardSamples) { + static_assert(sFilterDelay <= sMaxDelay, "Filter delay is too large"); + + const auto &PShift = GetPhaseShifter::Get(); + ASSUME(samplesToDo > 0); { @@ -240,3 +261,11 @@ void UhjStereoDecoder::decode(const al::span 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; +template struct UhjDecoder; +template struct UhjStereoDecoder; + +template struct UhjEncoder; +template struct UhjDecoder; +template struct UhjStereoDecoder; diff --git a/core/uhjfilter.h b/core/uhjfilter.h index eeabb6d2..c14ed5bf 100644 --- a/core/uhjfilter.h +++ b/core/uhjfilter.h @@ -9,30 +9,29 @@ #include "resampler_limits.h" -struct DecoderBase { - virtual ~DecoderBase() = default; +static constexpr size_t UhjLengthLq{256}; +static constexpr size_t UhjLengthHq{512}; +static constexpr size_t UhjLengthStd{UhjLengthLq}; - virtual void decode(const al::span samples, const size_t samplesToDo, - const size_t forwardSamples) = 0; - /** - * The width factor for Super Stereo processing. Can be changed in between - * calls to decode, with valid values being between 0...0.7. - */ - float mWidthControl{0.593f}; - - float mCurrentWidth{-1.0f}; -}; +struct UhjEncoderBase { + virtual ~UhjEncoderBase() = default; + virtual size_t getDelay() noexcept = 0; -struct UhjFilterBase { - /* The filter delay is half it's effective size, so a delay of 128 has a - * FIR length of 256. + /** + * 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). */ - static constexpr size_t sFilterDelay{128}; + virtual void encode(float *LeftOut, float *RightOut, + const al::span InSamples, const size_t SamplesToDo) = 0; }; -struct UhjEncoder : public UhjFilterBase { +template +struct UhjEncoder final : public UhjEncoderBase { + static constexpr size_t sFilterDelay{N/2}; + /* Delays and processing storage for the unfiltered signal. */ alignas(16) std::array mS{}; alignas(16) std::array mD{}; @@ -42,19 +41,41 @@ struct UhjEncoder : public UhjFilterBase { alignas(16) std::array 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 InSamples, - const size_t SamplesToDo); + const size_t SamplesToDo) override; DEF_NEWDEL(UhjEncoder) }; -struct UhjDecoder : public DecoderBase, public UhjFilterBase { +struct DecoderBase { + static constexpr size_t sMaxDelay{256}; + + virtual ~DecoderBase() = default; + + virtual void decode(const al::span samples, const size_t samplesToDo, + const size_t forwardSamples) = 0; + + /** + * The width factor for Super Stereo processing. Can be changed in between + * calls to decode, with valid values being between 0...0.7. + */ + float mWidthControl{0.593f}; + + float mCurrentWidth{-1.0f}; +}; + +template +struct UhjDecoder final : public DecoderBase { + static constexpr size_t sFilterDelay{N/2}; + /* 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 +struct UhjStereoDecoder final : public DecoderBase { + static constexpr size_t sFilterDelay{N/2}; + + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + + alignas(16) std::array mDTHistory{}; + alignas(16) std::array mSHistory{}; + + alignas(16) std::array 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 diff --git a/core/voice.cpp b/core/voice.cpp index ed6c9bf8..15230726 100644 --- a/core/voice.cpp +++ b/core/voice.cpp @@ -854,13 +854,13 @@ void Voice::prepare(DeviceBase *device) if(mFmtChannels == FmtSuperStereo) { - mDecoder = std::make_unique(); - mDecoderPadding = UhjStereoDecoder::sFilterDelay; + mDecoder = std::make_unique>(); + mDecoderPadding = UhjStereoDecoder::sFilterDelay; } else if(IsUHJ(mFmtChannels)) { - mDecoder = std::make_unique(); - mDecoderPadding = UhjDecoder::sFilterDelay; + mDecoder = std::make_unique>(); + mDecoderPadding = UhjDecoder::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::sWLFScale; + mChans[1].mAmbiLFScale = UhjDecoder::sXYLFScale; + mChans[2].mAmbiLFScale = UhjDecoder::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::sWLFScale; + mChans[1].mAmbiLFScale = UhjDecoder::sXYLFScale; + mChans[2].mAmbiLFScale = UhjDecoder::sXYLFScale; mFlags.set(VoiceIsAmbisonic); } else diff --git a/core/voice.h b/core/voice.h index 25560cb4..42ad7704 100644 --- a/core/voice.h +++ b/core/voice.h @@ -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 { -- cgit v1.2.3