#ifndef CORE_UHJFILTER_H #define CORE_UHJFILTER_H #include #include "almalloc.h" #include "alspan.h" #include "bufferline.h" static constexpr size_t UhjLength256{256}; static constexpr size_t UhjLength512{512}; enum class UhjQualityType : uint8_t { IIR = 0, FIR256, FIR512, Default = IIR }; extern UhjQualityType UhjDecodeQuality; extern UhjQualityType UhjEncodeQuality; struct UhjAllPassFilter { struct AllPassState { /* Last two delayed components for direct form II. */ std::array z{}; }; std::array mState; void processOne(const al::span coeffs, float x); void process(const al::span coeffs, const al::span src, const bool update, float *RESTRICT dst); }; 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 InSamples, const size_t SamplesToDo) = 0; }; template struct UhjEncoder final : public UhjEncoderBase { static constexpr size_t sFftLength{256}; static constexpr size_t sSegmentSize{sFftLength/2}; static constexpr size_t sNumSegments{N/sSegmentSize}; static constexpr size_t sFilterDelay{N/2 + sSegmentSize}; /* Delays and processing storage for the input signal. */ alignas(16) std::array mW{}; alignas(16) std::array mX{}; alignas(16) std::array mY{}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; /* History and temp storage for the convolution filter. */ size_t mFifoPos{}, mCurrentSegment{}; alignas(16) std::array mWXInOut{}; alignas(16) std::array mFftBuffer{}; alignas(16) std::array mWorkData{}; alignas(16) std::array mWXHistory{}; alignas(16) std::array,2> mDirectDelay{}; 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) override; }; struct UhjEncoderIIR final : public UhjEncoderBase { static constexpr size_t sFilterDelay{1}; /* Processing storage for the input signal. */ alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mWX{}; alignas(16) std::array mTemp{}; float mDelayWX{}, mDelayY{}; UhjAllPassFilter mFilter1WX; UhjAllPassFilter mFilter2WX; UhjAllPassFilter mFilter1Y; std::array mFilter1Direct; std::array mDirectDelay{}; 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) override; }; struct DecoderBase { static constexpr size_t sMaxPadding{256}; /* For 2-channel UHJ, shelf filters should use these LF responses. */ static constexpr float sWLFScale{0.661f}; static constexpr float sXYLFScale{1.293f}; virtual ~DecoderBase() = default; virtual void decode(const al::span samples, const size_t samplesToDo, const bool updateState) = 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}; }; template struct UhjDecoder final : public DecoderBase { /* The number of extra sample frames needed for input. */ static constexpr size_t sInputPadding{N/2}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mT{}; alignas(16) std::array mDTHistory{}; alignas(16) std::array mSHistory{}; alignas(16) std::array mTemp{}; /** * Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa * channel ordering and UHJ scaling. For 3-channel, the 3rd channel may be * attenuated by 'n', where 0 <= n <= 1. So to decode 2-channel UHJ, supply * 3 channels with the 3rd channel silent (n=0). The B-Format signal * reconstructed from 2-channel UHJ should not be run through a normal * B-Format decoder, as it needs different shelf filters. */ void decode(const al::span samples, const size_t samplesToDo, const bool updateState) override; }; struct UhjDecoderIIR final : public DecoderBase { /* These IIR decoder filters normally have a 1-sample delay on the non- * filtered components. However, the filtered components are made to skip * the first output sample and take one future sample, which puts it ahead * by one sample. The first filtered output sample is cut to align it with * the first non-filtered sample, similar to the FIR filters. */ static constexpr size_t sInputPadding{1}; bool mFirstRun{true}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2DT; UhjAllPassFilter mFilter1DT; UhjAllPassFilter mFilter2S; UhjAllPassFilter mFilter1Q; void decode(const al::span samples, const size_t samplesToDo, const bool updateState) override; }; template struct UhjStereoDecoder final : public DecoderBase { static constexpr size_t sInputPadding{N/2}; float mCurrentWidth{-1.0f}; 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 * should contain 3 channels, the first two being the left and right stereo * channels, and the third left empty. */ void decode(const al::span samples, const size_t samplesToDo, const bool updateState) override; }; struct UhjStereoDecoderIIR final : public DecoderBase { static constexpr size_t sInputPadding{1}; bool mFirstRun{true}; float mCurrentWidth{-1.0f}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2D; UhjAllPassFilter mFilter1D; UhjAllPassFilter mFilter2S; void decode(const al::span samples, const size_t samplesToDo, const bool updateState) override; }; #endif /* CORE_UHJFILTER_H */