diff options
author | Chris Robinson <[email protected]> | 2022-08-07 13:09:12 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2022-08-07 13:09:12 -0700 |
commit | 250f1624964c2c53e00d18fd1ec2bbc77c860298 (patch) | |
tree | c126c955a8871031d5da6d3c79fa4e7d380cc3e1 /core | |
parent | b77a556d7b7d1a66db4dcde0b77ea5d704254c13 (diff) |
Parameterize the UHJ filter length
Diffstat (limited to 'core')
-rw-r--r-- | core/device.h | 4 | ||||
-rw-r--r-- | core/uhjfilter.cpp | 37 | ||||
-rw-r--r-- | core/uhjfilter.h | 72 | ||||
-rw-r--r-- | core/voice.cpp | 20 | ||||
-rw-r--r-- | core/voice.h | 2 |
5 files changed, 98 insertions, 37 deletions
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<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; 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<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>; 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<float*> 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<const float*const,3> InSamples, const size_t SamplesToDo) = 0; }; -struct UhjEncoder : public UhjFilterBase { +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{}; @@ -42,19 +41,41 @@ struct UhjEncoder : public UhjFilterBase { 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); + 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<float*> 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<size_t N> +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<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 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<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 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 { |