1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
#ifndef CORE_UHJFILTER_H
#define CORE_UHJFILTER_H
#include <array>
#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<float,2> z{};
};
std::array<AllPassState,4> mState;
void processOne(const al::span<const float,4> coeffs, float x);
void process(const al::span<const float,4> coeffs, const al::span<const float> 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<const float*const,3> InSamples, const size_t SamplesToDo) = 0;
};
template<size_t N>
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<float,BufferLineSize+sFilterDelay> mW{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mX{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mY{};
alignas(16) std::array<float,BufferLineSize> mS{};
alignas(16) std::array<float,BufferLineSize> mD{};
/* History and temp storage for the convolution filter. */
size_t mFifoPos{}, mCurrentSegment{};
alignas(16) std::array<float,sFftLength> mWXInOut{};
alignas(16) std::array<float,sFftLength> mFftBuffer{};
alignas(16) std::array<float,sFftLength> mWorkData{};
alignas(16) std::array<float,sFftLength*sNumSegments> mWXHistory{};
alignas(16) std::array<std::array<float,sFilterDelay>,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<const float*const,3> 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<float,BufferLineSize+1> mS{};
alignas(16) std::array<float,BufferLineSize+1> mD{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mWX{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mTemp{};
float mDelayWX{}, mDelayY{};
UhjAllPassFilter mFilter1WX;
UhjAllPassFilter mFilter2WX;
UhjAllPassFilter mFilter1Y;
std::array<UhjAllPassFilter,2> mFilter1Direct;
std::array<float,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<const float*const,3> 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<float*> 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<size_t N>
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<float,BufferLineSize+sInputPadding> mS{};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mT{};
alignas(16) std::array<float,sInputPadding-1> mDTHistory{};
alignas(16) std::array<float,sInputPadding-1> mSHistory{};
alignas(16) std::array<float,BufferLineSize + sInputPadding*2> 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<float*> 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<float,BufferLineSize+sInputPadding> mS{};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mTemp{};
UhjAllPassFilter mFilter1S;
UhjAllPassFilter mFilter2DT;
UhjAllPassFilter mFilter1DT;
UhjAllPassFilter mFilter2S;
UhjAllPassFilter mFilter1Q;
void decode(const al::span<float*> samples, const size_t samplesToDo,
const bool updateState) override;
};
template<size_t N>
struct UhjStereoDecoder final : public DecoderBase {
static constexpr size_t sInputPadding{N/2};
float mCurrentWidth{-1.0f};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
alignas(16) std::array<float,sInputPadding-1> mDTHistory{};
alignas(16) std::array<float,sInputPadding-1> mSHistory{};
alignas(16) std::array<float,BufferLineSize + sInputPadding*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
* should contain 3 channels, the first two being the left and right stereo
* channels, and the third left empty.
*/
void decode(const al::span<float*> 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<float,BufferLineSize+sInputPadding> mS{};
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
alignas(16) std::array<float,BufferLineSize> mTemp{};
UhjAllPassFilter mFilter1S;
UhjAllPassFilter mFilter2D;
UhjAllPassFilter mFilter1D;
UhjAllPassFilter mFilter2S;
void decode(const al::span<float*> samples, const size_t samplesToDo,
const bool updateState) override;
};
#endif /* CORE_UHJFILTER_H */
|