Log the buffer format when queueing mismatched buffers
[openal-soft.git] / core / uhjfilter.cpp
blobdf50956a3b8c6847ac3b18dc77dcf3252c6edd8f
2 #include "config.h"
4 #include "uhjfilter.h"
6 #include <algorithm>
7 #include <iterator>
9 #include "alcomplex.h"
10 #include "alnumeric.h"
11 #include "opthelpers.h"
12 #include "phase_shifter.h"
15 UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
16 UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
19 namespace {
21 const PhaseShifterT<UhjLength256> PShiftLq{};
22 const PhaseShifterT<UhjLength512> PShiftHq{};
24 template<size_t N>
25 struct GetPhaseShifter;
26 template<>
27 struct GetPhaseShifter<UhjLength256> { static auto& Get() noexcept { return PShiftLq; } };
28 template<>
29 struct GetPhaseShifter<UhjLength512> { static auto& Get() noexcept { return PShiftHq; } };
32 constexpr float square(float x) noexcept
33 { return x*x; }
35 /* Filter coefficients for the 'base' all-pass IIR, which applies a frequency-
36 * dependent phase-shift of N degrees. The output of the filter requires a 1-
37 * sample delay.
39 constexpr std::array<float,4> Filter1Coeff{{
40 square(0.6923878f), square(0.9360654322959f), square(0.9882295226860f),
41 square(0.9987488452737f)
42 }};
43 /* Filter coefficients for the offset all-pass IIR, which applies a frequency-
44 * dependent phase-shift of N+90 degrees.
46 constexpr std::array<float,4> Filter2Coeff{{
47 square(0.4021921162426f), square(0.8561710882420f), square(0.9722909545651f),
48 square(0.9952884791278f)
49 }};
51 } // namespace
53 void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
54 const al::span<const float> src, const bool updateState, float *RESTRICT dst)
56 auto state = mState;
58 auto proc_sample = [&state,coeffs](float x) noexcept -> float
60 for(size_t i{0};i < 4;++i)
62 const float y{x*coeffs[i] + state[i].z[0]};
63 state[i].z[0] = state[i].z[1];
64 state[i].z[1] = y*coeffs[i] - x;
65 x = y;
67 return x;
69 std::transform(src.begin(), src.end(), dst, proc_sample);
70 if(updateState) LIKELY mState = state;
74 /* Encoding UHJ from B-Format is done as:
76 * S = 0.9396926*W + 0.1855740*X
77 * D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y
79 * Left = (S + D)/2.0
80 * Right = (S - D)/2.0
81 * T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y
82 * Q = 0.9772*Z
84 * where j is a wide-band +90 degree phase shift. 3-channel UHJ excludes Q,
85 * while 2-channel excludes Q and T.
87 * The phase shift is done using a linear FIR filter derived from an FFT'd
88 * impulse with the desired shift.
91 template<size_t N>
92 void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
93 const al::span<const float*const,3> InSamples, const size_t SamplesToDo)
95 const auto &PShift = GetPhaseShifter<N>::Get();
97 ASSUME(SamplesToDo > 0);
99 const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])};
100 const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])};
101 const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])};
103 std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay);
104 std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay);
105 std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay);
107 /* S = 0.9396926*W + 0.1855740*X */
108 for(size_t i{0};i < SamplesToDo;++i)
109 mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i];
111 /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
112 std::transform(winput, winput+SamplesToDo, xinput, mWX.begin() + sWXInOffset,
113 [](const float w, const float x) noexcept -> float
114 { return -0.3420201f*w + 0.5098604f*x; });
115 PShift.process({mD.data(), SamplesToDo}, mWX.data());
117 /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
118 for(size_t i{0};i < SamplesToDo;++i)
119 mD[i] = mD[i] + 0.6554516f*mY[i];
121 /* Copy the future samples to the front for next time. */
122 std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin());
123 std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin());
124 std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin());
125 std::copy(mWX.cbegin()+SamplesToDo, mWX.cbegin()+SamplesToDo+sWXInOffset, mWX.begin());
127 /* Apply a delay to the existing output to align with the input delay. */
128 auto *delayBuffer = mDirectDelay.data();
129 for(float *buffer : {LeftOut, RightOut})
131 float *distbuf{al::assume_aligned<16>(delayBuffer->data())};
132 ++delayBuffer;
134 float *inout{al::assume_aligned<16>(buffer)};
135 auto inout_end = inout + SamplesToDo;
136 if(SamplesToDo >= sFilterDelay) LIKELY
138 auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end);
139 std::swap_ranges(inout, delay_end, distbuf);
141 else
143 auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
144 std::rotate(distbuf, delay_start, distbuf + sFilterDelay);
148 /* Combine the direct signal with the produced output. */
150 /* Left = (S + D)/2.0 */
151 float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
152 for(size_t i{0};i < SamplesToDo;i++)
153 left[i] += (mS[i] + mD[i]) * 0.5f;
154 /* Right = (S - D)/2.0 */
155 float *RESTRICT right{al::assume_aligned<16>(RightOut)};
156 for(size_t i{0};i < SamplesToDo;i++)
157 right[i] += (mS[i] - mD[i]) * 0.5f;
160 /* This encoding implementation uses two sets of four chained IIR filters to
161 * produce the desired relative phase shift. The first filter chain produces a
162 * phase shift of varying degrees over a wide range of frequencies, while the
163 * second filter chain produces a phase shift 90 degrees ahead of the first
164 * over the same range. Further details are described here:
166 * https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/
168 * 2-channel UHJ output requires the use of three filter chains. The S channel
169 * output uses a Filter1 chain on the W and X channel mix, while the D channel
170 * output uses a Filter1 chain on the Y channel plus a Filter2 chain on the W
171 * and X channel mix. This results in the W and X input mix on the D channel
172 * output having the required +90 degree phase shift relative to the other
173 * inputs.
175 void UhjEncoderIIR::encode(float *LeftOut, float *RightOut,
176 const al::span<const float *const, 3> InSamples, const size_t SamplesToDo)
178 ASSUME(SamplesToDo > 0);
180 const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])};
181 const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])};
182 const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])};
184 /* S = 0.9396926*W + 0.1855740*X */
185 std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
186 [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; });
187 mFilter1WX.process(Filter1Coeff, {mTemp.data(), SamplesToDo}, true, mS.data()+1);
188 mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo];
190 /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */
191 std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
192 [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; });
193 mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, true, mWX.data());
195 /* Apply filter1 to Y and store in mD. */
196 mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1);
197 mD[0] = mDelayY; mDelayY = mD[SamplesToDo];
199 /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
200 for(size_t i{0};i < SamplesToDo;++i)
201 mD[i] = mWX[i] + 0.6554516f*mD[i];
203 /* Apply the base filter to the existing output to align with the processed
204 * signal.
206 mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1);
207 mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo];
209 /* Left = (S + D)/2.0 */
210 float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
211 for(size_t i{0};i < SamplesToDo;i++)
212 left[i] = (mS[i] + mD[i])*0.5f + mTemp[i];
214 mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+1);
215 mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo];
217 /* Right = (S - D)/2.0 */
218 float *RESTRICT right{al::assume_aligned<16>(RightOut)};
219 for(size_t i{0};i < SamplesToDo;i++)
220 right[i] = (mS[i] - mD[i])*0.5f + mTemp[i];
224 /* Decoding UHJ is done as:
226 * S = Left + Right
227 * D = Left - Right
229 * W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T)
230 * X = 0.418496*S - j(0.828331*D + 0.767820*T)
231 * Y = 0.795968*D - 0.676392*T + j(0.186633*S)
232 * Z = 1.023332*Q
234 * where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2-
235 * channel excludes Q and T.
237 template<size_t N>
238 void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
239 const bool updateState)
241 static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
243 const auto &PShift = GetPhaseShifter<N>::Get();
245 ASSUME(samplesToDo > 0);
248 const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
249 const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
250 const float *RESTRICT t{al::assume_aligned<16>(samples[2])};
252 /* S = Left + Right */
253 for(size_t i{0};i < samplesToDo+sInputPadding;++i)
254 mS[i] = left[i] + right[i];
256 /* D = Left - Right */
257 for(size_t i{0};i < samplesToDo+sInputPadding;++i)
258 mD[i] = left[i] - right[i];
260 /* T */
261 for(size_t i{0};i < samplesToDo+sInputPadding;++i)
262 mT[i] = t[i];
265 float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
266 float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
267 float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
269 /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
270 auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
271 std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sInputPadding, mT.cbegin(), tmpiter,
272 [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
273 if(updateState) LIKELY
274 std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin());
275 PShift.process({xoutput, samplesToDo}, mTemp.data());
277 /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */
278 for(size_t i{0};i < samplesToDo;++i)
279 woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i];
280 /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */
281 for(size_t i{0};i < samplesToDo;++i)
282 xoutput[i] = 0.418496f*mS[i] - xoutput[i];
284 /* Precompute j*S and store in youtput. */
285 tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
286 std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter);
287 if(updateState) LIKELY
288 std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin());
289 PShift.process({youtput, samplesToDo}, mTemp.data());
291 /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
292 for(size_t i{0};i < samplesToDo;++i)
293 youtput[i] = 0.795968f*mD[i] - 0.676392f*mT[i] + 0.186633f*youtput[i];
295 if(samples.size() > 3)
297 float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
298 /* Z = 1.023332*Q */
299 for(size_t i{0};i < samplesToDo;++i)
300 zoutput[i] = 1.023332f*zoutput[i];
304 void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
305 const bool updateState)
307 static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
309 ASSUME(samplesToDo > 0);
312 const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
313 const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
315 /* S = Left + Right */
316 for(size_t i{0};i < samplesToDo;++i)
317 mS[i] = left[i] + right[i];
319 /* D = Left - Right */
320 for(size_t i{0};i < samplesToDo;++i)
321 mD[i] = left[i] - right[i];
324 float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
325 float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
326 float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
328 /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
329 std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(),
330 [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
331 mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput);
333 /* Apply filter1 to S and store in mTemp. */
334 mTemp[0] = mDelayS;
335 mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
336 if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
338 /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */
339 for(size_t i{0};i < samplesToDo;++i)
340 woutput[i] = 0.981532f*mTemp[i] + 0.197484f*xoutput[i];
341 /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */
342 for(size_t i{0};i < samplesToDo;++i)
343 xoutput[i] = 0.418496f*mTemp[i] - xoutput[i];
346 /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */
347 std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput,
348 [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; });
349 mTemp[0] = mDelayDT;
350 mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()+1);
351 if(updateState) LIKELY mDelayDT = mTemp[samplesToDo];
353 /* Precompute j*S and store in youtput. */
354 mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
356 /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
357 for(size_t i{0};i < samplesToDo;++i)
358 youtput[i] = mTemp[i] + 0.186633f*youtput[i];
361 if(samples.size() > 3)
363 float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
365 /* Apply filter1 to Q and store in mTemp. */
366 mTemp[0] = mDelayQ;
367 mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()+1);
368 if(updateState) LIKELY mDelayQ = mTemp[samplesToDo];
370 /* Z = 1.023332*Q */
371 for(size_t i{0};i < samplesToDo;++i)
372 zoutput[i] = 1.023332f*mTemp[i];
377 /* Super Stereo processing is done as:
379 * S = Left + Right
380 * D = Left - Right
382 * W = 0.6098637*S - 0.6896511*j*w*D
383 * X = 0.8624776*S + 0.7626955*j*w*D
384 * Y = 1.6822415*w*D - 0.2156194*j*S
386 * where j is a +90 degree phase shift. w is a variable control for the
387 * resulting stereo width, with the range 0 <= w <= 0.7.
389 template<size_t N>
390 void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
391 const bool updateState)
393 static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
395 const auto &PShift = GetPhaseShifter<N>::Get();
397 ASSUME(samplesToDo > 0);
400 const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
401 const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
403 for(size_t i{0};i < samplesToDo+sInputPadding;++i)
404 mS[i] = left[i] + right[i];
406 /* Pre-apply the width factor to the difference signal D. Smoothly
407 * interpolate when it changes.
409 const float wtarget{mWidthControl};
410 const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
411 if(wtarget == wcurrent || !updateState)
413 for(size_t i{0};i < samplesToDo+sInputPadding;++i)
414 mD[i] = (left[i] - right[i]) * wcurrent;
415 mCurrentWidth = wcurrent;
417 else
419 const float wstep{(wtarget - wcurrent) / static_cast<float>(samplesToDo)};
420 float fi{0.0f};
421 for(size_t i{0};i < samplesToDo;++i)
423 mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
424 fi += 1.0f;
426 for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i)
427 mD[i] = (left[i] - right[i]) * wtarget;
428 mCurrentWidth = wtarget;
432 float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
433 float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
434 float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
436 /* Precompute j*D and store in xoutput. */
437 auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
438 std::copy_n(mD.cbegin(), samplesToDo+sInputPadding, tmpiter);
439 if(updateState) LIKELY
440 std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin());
441 PShift.process({xoutput, samplesToDo}, mTemp.data());
443 /* W = 0.6098637*S - 0.6896511*j*w*D */
444 for(size_t i{0};i < samplesToDo;++i)
445 woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i];
446 /* X = 0.8624776*S + 0.7626955*j*w*D */
447 for(size_t i{0};i < samplesToDo;++i)
448 xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i];
450 /* Precompute j*S and store in youtput. */
451 tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
452 std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter);
453 if(updateState) LIKELY
454 std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin());
455 PShift.process({youtput, samplesToDo}, mTemp.data());
457 /* Y = 1.6822415*w*D - 0.2156194*j*S */
458 for(size_t i{0};i < samplesToDo;++i)
459 youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i];
462 void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
463 const bool updateState)
465 static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
467 ASSUME(samplesToDo > 0);
470 const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
471 const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
473 for(size_t i{0};i < samplesToDo;++i)
474 mS[i] = left[i] + right[i];
476 /* Pre-apply the width factor to the difference signal D. Smoothly
477 * interpolate when it changes.
479 const float wtarget{mWidthControl};
480 const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
481 if(wtarget == wcurrent || !updateState)
483 for(size_t i{0};i < samplesToDo;++i)
484 mD[i] = (left[i] - right[i]) * wcurrent;
485 mCurrentWidth = wcurrent;
487 else
489 const float wstep{(wtarget - wcurrent) / static_cast<float>(samplesToDo)};
490 float fi{0.0f};
491 for(size_t i{0};i < samplesToDo;++i)
493 mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
494 fi += 1.0f;
496 mCurrentWidth = wtarget;
500 float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
501 float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
502 float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
504 /* Apply filter1 to S and store in mTemp. */
505 mTemp[0] = mDelayS;
506 mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
507 if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
509 /* Precompute j*D and store in xoutput. */
510 mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput);
512 /* W = 0.6098637*S - 0.6896511*j*w*D */
513 for(size_t i{0};i < samplesToDo;++i)
514 woutput[i] = 0.6098637f*mTemp[i] - 0.6896511f*xoutput[i];
515 /* X = 0.8624776*S + 0.7626955*j*w*D */
516 for(size_t i{0};i < samplesToDo;++i)
517 xoutput[i] = 0.8624776f*mTemp[i] + 0.7626955f*xoutput[i];
519 /* Precompute j*S and store in youtput. */
520 mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
522 /* Apply filter1 to D and store in mTemp. */
523 mTemp[0] = mDelayD;
524 mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()+1);
525 if(updateState) LIKELY mDelayD = mTemp[samplesToDo];
527 /* Y = 1.6822415*w*D - 0.2156194*j*S */
528 for(size_t i{0};i < samplesToDo;++i)
529 youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i];
533 template struct UhjEncoder<UhjLength256>;
534 template struct UhjDecoder<UhjLength256>;
535 template struct UhjStereoDecoder<UhjLength256>;
537 template struct UhjEncoder<UhjLength512>;
538 template struct UhjDecoder<UhjLength512>;
539 template struct UhjStereoDecoder<UhjLength512>;