JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
VBAPLUT2D.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright 2024 Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
22#include "JPLSpatial/Core.h"
26
35
37
38#include <cmath>
39#include <limits>
40#include <span>
41#include <type_traits>
42#include <bit>
43#include <vector>
44#include <cstring> // std::memcpy
45
46namespace JPL::VBAP
47{
48 //======================================================================
50 template<auto GetSpeakerAngleFunction>
51 class LUTBuilder2D;
52
53 //======================================================================
55 class LUT2D
56 {
57
58 public:
59 //==================================================================
60 /* Note on resolution:
61 (values in degrees)
62
63 at 256:
64 - LUT step mean width: 1.40625
65 - step variance: 0.288304
66 - step min: 0.909371
67 - step max: 1.78992
68 - step max-min: 0.880548
69
70 at 512:
71 - LUT step mean width: 0.703125
72 - step variance: 0.144059
73 - step min: 0.45112
74 - step max: 0.895192
75 - step max-min: 0.444072
76
77 at 1024:
78 - LUT step mean width: 0.351562
79 - step variance: 0.072
80 - step min: 0.224686
81 - step max: 0.447623
82 - step max-min: 0.222937
83
84 at 2048:
85 - LUT step mean width: 0.175781
86 - step variance: 0.0359921
87 - step min: 0.112124
88 - step max: 0.223812
89 - step max-min: 0.111687
90 */
91 struct LUTStats // These values were pre-computed in tests
92 {
93 static constexpr uint16 Resolution = 1024;
94 static constexpr float StepWidth = 0.351562f;
95 static constexpr float StepVariance = 0.072f;
96 static constexpr float StepMin = 0.224686f;
97 static constexpr float StepMax = 0.447623f;
98 static constexpr float StepMinMaxGap = StepMax - StepMin;
99 };
100
101 template<class T>
102 using Array = std::pmr::vector<T>;
103
104 //==================================================================
105 LUT2D() = default;
106
107 [[nodiscard]] JPL_INLINE bool IsInitialized() const noexcept { return !mData.empty(); }
108
120 [[nodiscard]] JPL_INLINE float GetLUTValue(uint32 positionInLUT) const { return mData[positionInLUT]; }
121
122 [[nodiscard]] JPL_INLINE float& operator[](int i) { return mData[i]; }
123 [[nodiscard]] JPL_INLINE const float& operator[](int i) const { return mData[i]; }
124
126 [[nodiscard]] JPL_INLINE size_t GetLUTSize() const noexcept { return mData.size(); }
127
129 [[nodiscard]] JPL_INLINE size_t GetLUTResolution() const noexcept { return mLUTResolution; }
130
133 [[nodiscard]] JPL_INLINE int AngleNormalizedToLUTPosition(float angleNormalised) const;
134
137 [[nodiscard]] JPL_INLINE int AngleToLUTPosition(float angleInRadians) const;
138
141 [[nodiscard]] JPL_INLINE float LUTPositionToAngle(int pos) const;
142
144 JPL_INLINE void GetSpeakerGains(int lutPosition, std::span<float> outGains) const;
145
147 JPL_INLINE void GetSpeakerGains(const Vec2& direction, std::span<float> outGains) const;
148
150 JPL_INLINE void GetSpeakerGains(const simd& dirX, const simd& dirY, std::span<simd> outGains) const;
151
153 [[nodiscard]] JPL_INLINE int CartesianToLUTPosition(float x, float y) const;
154
156 [[nodiscard]] JPL_INLINE simd_mask CartesianToLUTPosition(const simd& x, const simd& y) const;
157
160 static void BuildDiamondToUniformIndexLUT(Array<uint32>& outCorrectionLUT, uint32 N_uniform, uint32 M_corr = 1024);
161
164 static void BuildDiamondToAngleNormLUT(Array<float>& outCorrectionLUT, uint32 M_corr = 1024);
165
166 private:
167 void Resize(uint16 resolution, uint32 numTargetChannels);
168
169 private:
170 template<auto GetSpeakerAngleFunction>
171 friend class LUTBuilder2D;
173
176 uint16 mLUTResolution = 0;
177 uint16 mLUTResolutionMask = 0;
178 float mInvLUTResolution = 0.0f;
179 uint8 mNumTargetChannels = 0;
180 };
181
182 //=======================================================================
185 {
186 public:
187 JPL_INLINE explicit LUTQuery2D(const LUT2D& lut) noexcept : LUT(lut) {}
188
192 template<CVec3 Vec3Type>
193 JPL_INLINE void GainsFor(const Vec3Type& direction, std::span<float> outGains) const
194 {
195 // Normalize Vec2 we query.
197 Vec3Type dir(direction);
198 SetY(dir, 0.0f);
199 Normalize(dir);
200
201 LUT.GetSpeakerGains({
202 static_cast<float>(GetX(dir)),
203 static_cast<float>(GetZ(dir)) },
204 outGains);
205 }
206
212 JPL_INLINE void GainsFor(const simd& dirX, const simd& dirY, const simd& dirZ, std::span<simd> outGains) const
213 {
214 LUT.GetSpeakerGains(
215 dirX,
216 dirZ,
217 outGains);
218 }
219
220 public:
221 const LUT2D& LUT;
222 };
223
224 //======================================================================
227 template<auto GetSpeakerVectorFunction, auto GetSpeakerAngleFunction>
229 {
230 public:
231 using Vec3Type = std::remove_cvref_t<decltype(GetSpeakerVectorFunction(EChannel{}))>;
232 using LUTType = LUT2D;
235
236 //======================================================================
238 [[nodiscard]] static JPL_INLINE BuilderType MakeBuilder(ChannelMap channelMap, LUT2D& lut)
239 {
240 return BuilderType(channelMap, lut);
241 }
242
244 [[nodiscard]] static JPL_INLINE QueryType Query(const LUT2D& LUT)
245 {
246 return QueryType(LUT);
247 }
248 };
249
250 //======================================================================
252 template<auto GetSpeakerAngleFunction>
254 {
255 template<class T>
256 using Array = std::pmr::vector<T>;
257 using ChannelAngleArray = Array<ChannelAngle>;
258 using LUTType = LUT2D;
259
260 public:
261 LUTBuilder2D(ChannelMap channelMap, LUTType& LUT);
262
266 [[nodiscard]] JPL_INLINE bool RequiresChannelConversion() const noexcept { return mChannelMapInternal != mChannelMapTarget; }
267
269 [[nodiscard]] float FindShortestAperture() const;
270
272 [[nodiscard]] bool ComputeCellFor(const Vec2& direction, int lutOffset);
273
275 [[nodiscard]] bool BuildForAllDirections();
276
277#if JPL_VALIDATE_VBAP_LUT
278 void ValidateLUT() const;
279#endif
280
281 private:
282 void ComputePairMatrices();
283
284 // 'ThisType' is to deduce constness and avoid two otherwise identical member functions.
285 // "Deducing this" is not available until C++23.
286 template<class ThisType, class CallbackType>
287 static void ForEachChannelAnglePair(ThisType& self, CallbackType&& callback);
288
289 // If target channel map requires conversion (i.e. if it has < 4 channels, and we use intermediary quad map),
290 // then we call this function to convert and store converted gains in the LUT
291 JPL_INLINE void StoreChannelGainsConverted(uint32 channelId1, uint32 channelId2, const Vec2& gains, uint32 lutOffset);
292
293 void ApplyChannelConversion(const std::pair<uint32, uint32>& inChannelIds, const Vec2& inGains, std::span<float> outValues) const;
294
295 private:
296 LUT2D& mLUT;
297
298 ChannelMap mChannelMapInternal;
299 ChannelMap mChannelMapTarget;
300
301 uint32 mNumInternalChannels;
302 uint32 mNumTargetChannels;
303 uint32 mLFEIndex;
304
305 Array<ChannelAngle> mChannelAngels{ GetDefaultMemoryResource() };
306
307 ChannelConversionWeights mChannelConversionWeights;
308
309 struct ChannelPair
310 {
311 Math::Mat2<Vec2> invL;
312 uint32 ChannelId1;
313 uint32 ChannelId2;
314 };
315
316 Array<ChannelPair> mChannelPairs{ GetDefaultMemoryResource() };
317 };
318} // namespace JPL::VBAP
319
320//==============================================================================
321//
322// Code beyond this point is implementation detail...
323//
324//==============================================================================
325namespace JPL::VBAP
326{
327 //==========================================================================
328 JPL_INLINE int LUT2D::AngleNormalizedToLUTPosition(float angleNormalised) const
329 {
331
332 return static_cast<int>(
333 ToDiamond(Vec2{ sinf(angleNormalised), -cosf(angleNormalised) })
334 * mLUTResolution + 0.5f
335 ) & mLUTResolutionMask;
336 }
337
338 JPL_INLINE int LUT2D::AngleToLUTPosition(float angleInRadians) const
339 {
340 // Normalize to [0, 2Pi]
341 if (angleInRadians < float(0.0))
342 angleInRadians += JPL_TWO_PI;
343 return AngleNormalizedToLUTPosition(angleInRadians);
344 }
345
346 JPL_INLINE int LUT2D::CartesianToLUTPosition(float x, float y) const
347 {
348#if 1 // The LUT built with uniform steps of diamond encoding
349 const float diamond = ToDiamond(Vec2(x, y));
350 return static_cast<int>(Math::FMA(static_cast<float>(mLUTResolution), diamond, 0.5f)) & mLUTResolutionMask;
351#elif 0
352 JPL_ASSERT(!mDiamondCorrectionLUT.empty());
353
354 // Diamond to uniform index
355 const float diamond = ToDiamond(Vec2(x, y));
356 const auto angleT = static_cast<int>(diamond * sCorrectionLUTSize + 0.5f) & sCorrectionLUTMask;
357
358 //return static_cast<int>(diamond * mLUTResolution + 0.5f) & mLUTResolutionMask;
359 return mDiamondCorrectionLUT[angleT];
360
361#if 0 // If correction LUT is just uniforming the angles, not directly to target indices
362 // normalized angle in [0,1)
363 const float uniformAngleT = mDiamondCorrectionLUT[angleT];
364
365 // final uniform-angle index in [0, mLUTResolution)
366 return static_cast<int>(uniformAngleT * mLUTResolution + 0.5f) & mLUTResolutionMask;
367#endif
368
369#else
370 // Angle in [-Pi, Pi]
371 const float angle = std::atan2(x, z);
372 return AngleToLUTPosition(angle);
373#endif
374 }
375
376 JPL_INLINE simd_mask LUT2D::CartesianToLUTPosition(const simd& x, const simd& y) const
377 {
379 const simd invLen = Math:: InvSqrtFast(x * x + y * y);
380 const simd xN = x * invLen;
381 const simd yN = y * invLen;
382
383 const simd diamond = ToDiamond(xN, yN);
384 return Math::FMA(diamond, simd(mLUTResolution), 0.5f).to_mask() & mLUTResolutionMask;
385 }
386
387 inline void LUT2D::Resize(uint16 resolution, uint32 numTargetChannels)
388 {
389 // We should not have more than 255 channels
390 JPL_ASSERT(numTargetChannels <= std::numeric_limits<uint8>::max());
391
392 // for the sake of sanity
393 resolution = std::bit_ceil(resolution);
394
395 mData.clear();
396 mData.resize(numTargetChannels * resolution, 0.0f);
397
398 mLUTResolution = resolution;
399 mLUTResolutionMask = mLUTResolution - 1;
400 mInvLUTResolution = 1.0f / mLUTResolution;
401 mNumTargetChannels = static_cast<uint8>(numTargetChannels);
402 }
403
404 JPL_INLINE auto LUT2D::LUTPositionToAngle(int pos) const -> float
405 {
406 const Vec2 direction = FromDiamond((static_cast<float>(pos) * static_cast<float>(mInvLUTResolution)));
407 float angle = atan2f(direction.Y, direction.X);
408 if (angle < 0.0f)
409 angle += JPL_TWO_PI;
410 return angle;
411 }
412
413 JPL_INLINE void LUT2D::GetSpeakerGains(int lutPosition, std::span<float> outGains) const
414 {
415 JPL_ASSERT(outGains.size() <= mNumTargetChannels);
416
417 const float* speakerGain = &mData[mNumTargetChannels * lutPosition];
418 std::memcpy(outGains.data(), speakerGain, sizeof(float) * outGains.size());
419 }
420
421 JPL_INLINE void LUT2D::GetSpeakerGains(const Vec2& direction, std::span<float> outGains) const
422 {
423 GetSpeakerGains(CartesianToLUTPosition(direction.X, direction.Y), outGains);
424 }
425
426 JPL_INLINE void LUT2D::GetSpeakerGains(const simd& dirX, const simd& dirY, std::span<simd> outGains) const
427 {
428 // For each speaker we compute 4 directions.
429 // We can vectorize the encoding of the direction into LUT index,
430 // however we have to retrieve the channel gains individually per direction index,
431 // since the location for each simd lane in LUT differs.
432
433 uint32 lutPositions[4];
434 (CartesianToLUTPosition(dirX, dirY) * mNumTargetChannels).store(lutPositions);
435
436 static constexpr std::size_t bufferSize = 32 * simd::size();
437 JPL_ASSERT(bufferSize >= outGains.size() * simd::size());
438
439 float buffer[bufferSize];
440 const uint32 offsets[]{
441 0,
442 mNumTargetChannels,
443 static_cast<uint32>(mNumTargetChannels) << 1,
444 (static_cast<uint32>(mNumTargetChannels) << 1) + mNumTargetChannels
445 };
446
447 // Retrieve gains from the LUT and write contiguously
448 // d1[ch1, ch2], dr2[ch1, ch2]...
449 for (uint32 i = 0, dest = 0; i < simd_mask::size(); ++i, dest += mNumTargetChannels)
450 {
451 std::memcpy(&buffer[dest], &mData[lutPositions[i]], sizeof(float) * mNumTargetChannels);
452 }
453
454 // Copy gains from the buffer strided into out simd lanes
455 for (uint32 si = 0; si < outGains.size(); ++si)
456 {
457 // dr1[ch1], dr2[ch1], dr3[ch1], dr4[ch1]
458 outGains[si] = simd(buffer[si], buffer[si + offsets[1]], buffer[si + offsets[2]], buffer[si + offsets[3]]);
459 }
460 }
461
462 inline void LUT2D::BuildDiamondToUniformIndexLUT(Array<uint32>& outCorrectionLut, uint32 N_uniform, uint32 M_corr /*= 1024*/)
463 {
464 // Require power-of-two sizes for cheap masking
465 auto isPow2 = [](uint32 n) { return n && ((n & (n - 1)) == 0); };
466 JPL_ASSERT(isPow2(N_uniform) && "N_uniform must be power-of-two");
467 JPL_ASSERT(isPow2(M_corr) && "M_corr must be power-of-two");
468
469 outCorrectionLut.resize(M_corr);
470
471 const float invM_Corr = 1.0f / M_corr;
472
473 for (uint32 j = 0; j < M_corr; ++j)
474 {
475 // diamond parameter for this table cell
476 const float p = static_cast<float>(j) * invM_Corr;
477
478 // decode to unit vector on circle
479 const Vec2 v = FromDiamond(p);
480
481 // recover true polar angle in [theta, 2PI)
482 float theta = std::atan2(v.X, v.Y);
483 if (theta < 0.0f)
484 theta += JPL_TWO_PI;
485
486 // map to our uniform-angle LUT index
487 const float t = (theta * JPL_INV_TWO_PI) * static_cast<float>(N_uniform);
488 const auto idx = static_cast<uint32>(std::llround(t)) & (N_uniform - 1);
489
490 outCorrectionLut[j] = idx;
491 }
492 }
493
494 inline void LUT2D::BuildDiamondToAngleNormLUT(Array<float>& outCorrectionLUT, uint32 M_corr)
495 {
496 auto isPow2 = [](uint32_t n) { return n && ((n & (n - 1)) == 0); };
497 JPL_ASSERT(isPow2(M_corr));
498
499 outCorrectionLUT.resize(M_corr);
500
501 const float invM_Corr = 1.0f / M_corr;
502
503 for (uint32_t j = 0; j < M_corr; ++j)
504 {
505 // diamond parameter for this table cell
506 const float p = static_cast<float>(j) * invM_Corr;
507
508 // decode to unit vector on circle
509 const Vec2 v = FromDiamond(p);
510
511 float theta = std::atan2(v.X, v.Y); // (-PI, PI]
512 if (theta < 0.0f)
513 theta += JPL_TWO_PI; // [0, 2PI)
514
515 outCorrectionLUT[j] = theta * JPL_INV_TWO_PI; // normalized angle
516 }
517 }
518
519
520#if 0
521 JPL_INLINE float CircularLerp01(float a, float b, float t)
522 {
523 // both in [0,1); treat 1 as 0 on the circle
524 float d = b - a;
525 if (d > 0.5f) d -= 1.0f;
526 if (d < -0.5f) d += 1.0f;
527 float u = a + t * d;
528 if (u < 0.0f) u += 1.0f;
529 if (u >= 1.0f) u -= 1.0f;
530 return u;
531 }
532#endif
533
534 //==========================================================================
535 template<auto GetSpeakerAngleFunction>
537 : mLUT(LUT)
538 {
539 mChannelMapTarget = channelMap;
540 mNumTargetChannels = channelMap.GetNumChannels();
541
542 // We need at least quad layout for VBAP to work
543 mChannelMapInternal = channelMap.GetNumChannels() < 4 ? ChannelMap::FromChannelMask(ChannelMask::Quad) : channelMap;
544 mNumInternalChannels = mChannelMapInternal.GetNumChannels();
545 mLFEIndex = mChannelMapInternal.GetChannelIndex(EChannel::LFE);
546
547 // Extract sortet speaker angles
548 static constexpr bool skipLFE = false;
549 VBAP::ChannelAngle::GetSortedChannelAngles(mChannelMapInternal, mChannelAngels, GetSpeakerAngleFunction, skipLFE);
550 JPL_ASSERT(mChannelAngels.size() == mNumInternalChannels);
551
552 //JPL_ASSERT(intermNumChannels <= Traits::MAX_CHANNELS);
553
554 ComputePairMatrices();
555
557 {
558 mChannelConversionWeights.Resize(mNumTargetChannels, mNumInternalChannels);
559 ComputeChannelConversionRectangularWeights(mChannelMapInternal, mChannelMapTarget, mChannelConversionWeights);
560 }
561
562 mLUT.Resize(LUTType::LUTStats::Resolution, mNumTargetChannels);
563 }
564
565 template<auto GetSpeakerAngleFunction>
567 {
568 float shortestAperture = std::numeric_limits<float>::max();
569
570 const uint32 lastSpeakerId = mChannelAngels.back().ChannelId;
571
572 auto findShortestAperture = [&](const ChannelAngle& cha1, const ChannelAngle& cha2)
573 {
574 if (cha1.ChannelId == lastSpeakerId)
575 shortestAperture = std::min(shortestAperture, JPL_TWO_PI - cha1.Angle + cha2.Angle);
576 else
577 shortestAperture = std::min(shortestAperture, cha2.Angle - cha1.Angle);
578 };
579
580 ForEachChannelAnglePair(*this, findShortestAperture);
581
582 return shortestAperture;
583 }
584
585 template<auto GetSpeakerAngleFunction>
586 inline bool LUTBuilder2D<GetSpeakerAngleFunction>::ComputeCellFor(const Vec2& direction, int lutOffset)
587 {
588 // Assign speaker contribution values
589 for (const ChannelPair& pair : mChannelPairs)
590 {
591 Vec2 gains = pair.invL.Transform(direction);
592
593 if (gains.X < 0.0f || gains.Y < 0.0f)
594 continue; // not our pair
595
596 gains.Normalize();
597
598 if (RequiresChannelConversion())
599 {
600 StoreChannelGainsConverted(pair.ChannelId1, pair.ChannelId2, gains, lutOffset);
601 }
602 else
603 {
606 mLUT.mData[lutOffset + pair.ChannelId1] = gains.X;
607 mLUT.mData[lutOffset + pair.ChannelId2] = gains.Y;
608 }
609
610 return true;
611 }
612
613 // This should be unreachable
614 JPL_ASSERT(false);
615 return false;
616 }
617
618 template<auto GetSpeakerAngleFunction>
620 {
621 bool bAnyFailed = false;
622
623 // Build a LUT with uniform diamond steps
624 const float step = 1.0f / mLUT.GetLUTResolution();
625 float diamond = 0.0f;
626
627 for (uint32 pos = 0; pos < mLUT.GetLUTResolution(); ++pos, diamond += step)
628 {
629 JPL_ASSERT(diamond <= 1.0f);
630
631 const Vec2 direction = FromDiamond(diamond);
632
633 // Position of the next diamond step value in the LUT
634 // (number of channels stride)
635 const uint32 offset = mNumTargetChannels * pos;
636
637 // TODO: we may or may not want to terminate if any cell fails
638 bAnyFailed |= ComputeCellFor(direction, offset);
639 }
640
641 return bAnyFailed;
642 }
643
644 template<auto GetSpeakerAngleFunction>
646 {
647 auto makeInvMat = [&](const ChannelAngle& cha1, const ChannelAngle& cha2)
648 {
649 //JPL_ASSERT(Math::IsPositiveAndBelow(cha2.Angle - cha1.Angle, JPL_PI + 1e-6f));
650
651 const auto [s1, c1] = Math::SinCos(cha1.Angle);
652 const auto [s2, c2] = Math::SinCos(cha2.Angle);
653
654 const auto mat =
656 Vec2(s1, -c1),
657 Vec2(s2, -c2));
658
659 // Inverse will fail for wide aperture angles,
660 // therefore we need at least quad layout for <= 90 degree max aperture
661 Math::Mat2<Vec2> invL;
662 (void)JPL_ENSURE(mat.TryInverse(invL));
663
664 mChannelPairs.emplace_back(
665 invL,
666 cha1.ChannelId,
667 cha2.ChannelId);
668 };
669
670 ForEachChannelAnglePair(*this, makeInvMat);
671 }
672
673 template<auto GetSpeakerAngleFunction>
674 template<class ThisType, class CallbackType>
675 inline void LUTBuilder2D<GetSpeakerAngleFunction>::ForEachChannelAnglePair(ThisType& self,
676 CallbackType&& callback)
677 {
678 auto sanitizeLFEIndex = [&self](uint32 idx)
679 {
680 return idx + (self.mChannelAngels[idx].ChannelId == self.mLFEIndex);
681 };
682
683 for (uint32 ch = 0; ch < self.mChannelAngels.size() - 1; ++ch)
684 {
685 const uint32 ch1 = sanitizeLFEIndex(ch);
686 const uint32 ch2 = sanitizeLFEIndex(ch1 + 1);
687
688 const ChannelAngle& cha1 = self.mChannelAngels[ch1];
689 const ChannelAngle& cha2 = self.mChannelAngels[ch2];
690 callback(cha1, cha2);
691 }
692
693 // Handle wrap around
694 {
695 const uint32 ch1 = static_cast<uint32>(self.mChannelAngels.size()) - 1;
696 const uint32 ch2 = sanitizeLFEIndex(0);
697 const ChannelAngle& cha1 = self.mChannelAngels[ch1];
698 const ChannelAngle& cha2 = self.mChannelAngels[ch2];
699 callback(cha1, cha2);
700 }
701 }
702
703 template<auto GetSpeakerAngleFunction>
704 JPL_INLINE void LUTBuilder2D<GetSpeakerAngleFunction>::StoreChannelGainsConverted(uint32 channelId1,
705 uint32 channelId2,
706 const Vec2& gains,
707 uint32 lutOffset)
708 {
709 std::span<float> targetGains(&mLUT.mData[lutOffset], mNumTargetChannels);
710
711 // Convert from intermediary to target
712 // channel map that we store in the LUT
713 ApplyChannelConversion({ channelId1, channelId2 }, gains, targetGains);
714
715 // Normalize
717 Algo::NormalizeL2(targetGains);
718 }
719
720 template<auto GetSpeakerAngleFunction>
721 inline void LUTBuilder2D<GetSpeakerAngleFunction>::ApplyChannelConversion(const std::pair<uint32, uint32>& inChannelIds,
722 const Vec2& inGains,
723 std::span<float> outValues) const
724 {
725 for (uint32 iChannelOut = 0; iChannelOut < outValues.size(); ++iChannelOut)
726 {
727 const float accumulation =
728 inGains.X * mChannelConversionWeights[iChannelOut][inChannelIds.first] +
729 inGains.Y * mChannelConversionWeights[iChannelOut][inChannelIds.second];
730
731 outValues[iChannelOut] = accumulation;
732 }
733 }
734
735#if JPL_VALIDATE_VBAP_LUT
736 template<auto GetSpeakerAngleFunction>
737 void LUTBuilder2D<GetSpeakerAngleFunction>::ValidateLUT() const
738 {
739
740#if 0 // TODO: implement for 2D
741
742 JPL_ASSERT(mLUT != nullptr);
743
744 for (uint32 i = 0; i < mLUT->Speakers.size(); ++i)
745 {
746 if (!LUTCodec::IsValidCode(i))
747 continue;
748
749 const auto& speakers = mLUT->Speakers[i];
750 JPL_ASSERT(speakers[0] != speakers[1]);
751 JPL_ASSERT(speakers[1] != speakers[2]);
752 JPL_ASSERT(speakers[2] != speakers[0]);
753 }
754
755 for (uint32 i = 0; i < mLUT->Gains.size(); ++i)
756 {
757 if (!LUTCodec::IsValidCode(i))
758 continue;
759
760 const auto& gains = mLUT->Gains[i];
761 // Gains can be encoded in 24 or even 16 bit
762 const std::array<float, 3> gainsDecoded
763 {
764 gains[0],
765 gains[1],
766 gains[2]
767 };
768 JPL_ASSERT(Algo::IsNormalizedL2(gainsDecoded));
769 }
770#endif
771 }
772#endif
773
774} // namespace JPL::VBAP
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:80
#define JPL_ENSURE(inExpression,...)
Define ENSURE.
Definition ErrorReporting.h:94
Utility helper to access 2D array kind of weights.
Definition ChannelConversion.h:100
JPL_INLINE void Resize(uint32 numOutputs, uint32 numInputs)
Definition ChannelConversion.h:113
Definition ChannelMap.h:154
constexpr uint32 GetNumChannels() const noexcept
Definition ChannelMap.h:166
static constexpr ChannelMap FromChannelMask(uint32 channelMask)
Definition ChannelMap.h:202
constexpr uint32 GetChannelIndex(EChannel channel) const
Definition ChannelMap.h:167
Look-up table containing channel gains for each direction.
Definition VBAPLUT2D.h:56
JPL_INLINE int AngleNormalizedToLUTPosition(float angleNormalised) const
Definition VBAPLUT2D.h:328
JPL_INLINE bool IsInitialized() const noexcept
Definition VBAPLUT2D.h:107
static void BuildDiamondToAngleNormLUT(Array< float > &outCorrectionLUT, uint32 M_corr=1024)
Definition VBAPLUT2D.h:494
std::pmr::vector< T > Array
Definition VBAPLUT2D.h:102
JPL_INLINE size_t GetLUTSize() const noexcept
Size of the LUT = LUT Resolution * Number of Channels.
Definition VBAPLUT2D.h:126
JPL_INLINE int CartesianToLUTPosition(float x, float y) const
Get LUT position from direction vector.
Definition VBAPLUT2D.h:346
static void BuildDiamondToUniformIndexLUT(Array< uint32 > &outCorrectionLUT, uint32 N_uniform, uint32 M_corr=1024)
Definition VBAPLUT2D.h:462
JPL_INLINE size_t GetLUTResolution() const noexcept
Resolution of the LUT.
Definition VBAPLUT2D.h:129
JPL_INLINE float & operator[](int i)
Definition VBAPLUT2D.h:122
JPL_INLINE const float & operator[](int i) const
Definition VBAPLUT2D.h:123
JPL_INLINE int AngleToLUTPosition(float angleInRadians) const
Definition VBAPLUT2D.h:338
JPL_INLINE float LUTPositionToAngle(int pos) const
Definition VBAPLUT2D.h:404
JPL_INLINE void GetSpeakerGains(int lutPosition, std::span< float > outGains) const
Get preprocessed speaker gains at specific LUT posotion.
Definition VBAPLUT2D.h:413
JPL_INLINE float GetLUTValue(uint32 positionInLUT) const
Definition VBAPLUT2D.h:120
Forward declarations.
Definition VBAPLUT2D.h:254
bool BuildForAllDirections()
Build the entire LUT for all directions.
Definition VBAPLUT2D.h:619
LUTBuilder2D(ChannelMap channelMap, LUTType &LUT)
Definition VBAPLUT2D.h:536
bool ComputeCellFor(const Vec2 &direction, int lutOffset)
Comput LUT gains for given direction and LUT offset.
Definition VBAPLUT2D.h:586
JPL_INLINE bool RequiresChannelConversion() const noexcept
Definition VBAPLUT2D.h:266
float FindShortestAperture() const
Find shortest aperture between two speakers of the target map.
Definition VBAPLUT2D.h:566
Definition VBAPLUT2D.h:229
static JPL_INLINE QueryType Query(const LUT2D &LUT)
Make LUTQuery object to query 'LUT' for speaker gains.
Definition VBAPLUT2D.h:244
std::remove_cvref_t< decltype(GetSpeakerVectorFunction(EChannel{}))> Vec3Type
Definition VBAPLUT2D.h:231
LUTBuilder2D< GetSpeakerAngleFunction > BuilderType
Definition VBAPLUT2D.h:233
static JPL_INLINE BuilderType MakeBuilder(ChannelMap channelMap, LUT2D &lut)
Make LUTBuilder object to build LUT for given 'channelMap' and 'LUTType'.
Definition VBAPLUT2D.h:238
LUTQuery2D QueryType
Definition VBAPLUT2D.h:234
Interface to query LUT gains for a direction.
Definition VBAPLUT2D.h:185
JPL_INLINE void GainsFor(const Vec3Type &direction, std::span< float > outGains) const
Definition VBAPLUT2D.h:193
JPL_INLINE void GainsFor(const simd &dirX, const simd &dirY, const simd &dirZ, std::span< simd > outGains) const
Definition VBAPLUT2D.h:212
JPL_INLINE LUTQuery2D(const LUT2D &lut) noexcept
Definition VBAPLUT2D.h:187
const LUT2D & LUT
Definition VBAPLUT2D.h:221
JPL_INLINE constexpr void NormalizeL2(ContainerType &&data)
Apply unit vector scaling, so that the magnitude of the vector = 1.
Definition Algorithm.h:98
JPL_INLINE constexpr bool IsNormalizedL2(const ContainerType &data, float tolerance=JPL_FLOAT_EPS)
Definition Algorithm.h:115
constexpr uint32 Quad
Definition ChannelMap.h:101
JPL_INLINE constexpr T FMA(T a, T b, T c) noexcept
Inlined fuse multiply-add. Compiler in some circumstances is more eager to optimize this than std::fm...
Definition Math.h:186
JPL_INLINE simd InvSqrtFast(const simd &vec) noexcept
Definition SIMD.h:1783
JPL_INLINE std::pair< T, T > SinCos(T value) noexcept
Definition Math.h:164
Forward declaration.
Definition DummySpeakers.h:31
std::uint32_t uint32
Definition Core.h:311
constexpr Vec2 FromDiamond(float p) noexcept
Decode scalar [0, 1] to a 2D unit vector.
Definition DirectionEncoding.h:212
JPL_INLINE auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
JPL_INLINE void SetY(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:40
std::uint8_t uint8
Definition Core.h:309
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
EChannel
Definition ChannelMap.h:39
@ LFE
Definition ChannelMap.h:43
constexpr float ToDiamond(Vec2 dir) noexcept
"Diamond Encoding" of a 2D unit vector as per:
Definition DirectionEncoding.h:197
std::uint16_t uint16
Definition Core.h:310
Minimal 2x2 matrix interface.
Definition MinimalMat.h:35
static JPL_INLINE constexpr Mat2 FromColumns(const Vec2 &l1, const Vec2 &l2) noexcept
Definition MinimalMat.h:42
Definition VBAPEx.h:241
float Angle
Definition VBAPEx.h:242
uint32 ChannelId
Definition VBAPEx.h:243
static void GetSortedChannelAngles(ChannelMap channelMap, ArrayType< ChannelAngle, Args... > &sortedChannelAngles, std::function< float(EChannel)> getChannelAngle, bool skipLFE=true)
Get channel angles from ChannelMap, normalize to [0, Pi] and sort in assending order.
Definition VBAPEx.h:258
Definition VBAPLUT2D.h:92
static constexpr float StepVariance
Definition VBAPLUT2D.h:95
static constexpr float StepMinMaxGap
Definition VBAPLUT2D.h:98
static constexpr float StepMax
Definition VBAPLUT2D.h:97
static constexpr float StepMin
Definition VBAPLUT2D.h:96
static constexpr float StepWidth
Definition VBAPLUT2D.h:94
static constexpr uint16 Resolution
Definition VBAPLUT2D.h:93
Definition VBAPLUT3D.h:112
Definition MinimalVec2.h:29
JPL_INLINE constexpr Vec2 & Normalize() noexcept
Definition MinimalVec2.h:37
float X
Definition MinimalVec2.h:30
float Y
Definition MinimalVec2.h:31
Definition SIMD.h:207
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:231
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97