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 bool bHeadphonesProfile;
305
306 Array<ChannelAngle> mChannelAngels{ GetDefaultMemoryResource() };
307
308 ChannelConversionWeights mChannelConversionWeights;
309
310 struct ChannelPair
311 {
312 Math::Mat2<Vec2> invL;
313 uint32 ChannelId1;
314 uint32 ChannelId2;
315 };
316
317 Array<ChannelPair> mChannelPairs{ GetDefaultMemoryResource() };
318 };
319} // namespace JPL::VBAP
320
321//==============================================================================
322//
323// Code beyond this point is implementation detail...
324//
325//==============================================================================
326namespace JPL::VBAP
327{
328 //==========================================================================
329 JPL_INLINE int LUT2D::AngleNormalizedToLUTPosition(float angleNormalised) const
330 {
332
333 return static_cast<int>(
334 ToDiamond(Vec2{ sinf(angleNormalised), -cosf(angleNormalised) })
335 * mLUTResolution + 0.5f
336 ) & mLUTResolutionMask;
337 }
338
339 JPL_INLINE int LUT2D::AngleToLUTPosition(float angleInRadians) const
340 {
341 // Normalize to [0, 2Pi]
342 if (angleInRadians < float(0.0))
343 angleInRadians += JPL_TWO_PI;
344 return AngleNormalizedToLUTPosition(angleInRadians);
345 }
346
347 JPL_INLINE int LUT2D::CartesianToLUTPosition(float x, float y) const
348 {
349#if 1 // The LUT built with uniform steps of diamond encoding
350 const float diamond = ToDiamond(Vec2(x, y));
351 return static_cast<int>(Math::FMA(static_cast<float>(mLUTResolution), diamond, 0.5f)) & mLUTResolutionMask;
352#elif 0
353 JPL_ASSERT(!mDiamondCorrectionLUT.empty());
354
355 // Diamond to uniform index
356 const float diamond = ToDiamond(Vec2(x, y));
357 const auto angleT = static_cast<int>(diamond * sCorrectionLUTSize + 0.5f) & sCorrectionLUTMask;
358
359 //return static_cast<int>(diamond * mLUTResolution + 0.5f) & mLUTResolutionMask;
360 return mDiamondCorrectionLUT[angleT];
361
362#if 0 // If correction LUT is just uniforming the angles, not directly to target indices
363 // normalized angle in [0,1)
364 const float uniformAngleT = mDiamondCorrectionLUT[angleT];
365
366 // final uniform-angle index in [0, mLUTResolution)
367 return static_cast<int>(uniformAngleT * mLUTResolution + 0.5f) & mLUTResolutionMask;
368#endif
369
370#else
371 // Angle in [-Pi, Pi]
372 const float angle = std::atan2(x, z);
373 return AngleToLUTPosition(angle);
374#endif
375 }
376
377 JPL_INLINE simd_mask LUT2D::CartesianToLUTPosition(const simd& x, const simd& y) const
378 {
380 const simd invLen = Math:: InvSqrtFast(x * x + y * y);
381 const simd xN = x * invLen;
382 const simd yN = y * invLen;
383
384 const simd diamond = ToDiamond(xN, yN);
385 return Math::FMA(diamond, simd(mLUTResolution), 0.5f).to_mask() & mLUTResolutionMask;
386 }
387
388 inline void LUT2D::Resize(uint16 resolution, uint32 numTargetChannels)
389 {
390 // We should not have more than 255 channels
391 JPL_ASSERT(numTargetChannels <= std::numeric_limits<uint8>::max());
392
393 // for the sake of sanity
394 resolution = std::bit_ceil(resolution);
395
396 mData.clear();
397 mData.resize(numTargetChannels * resolution, 0.0f);
398
399 mLUTResolution = resolution;
400 mLUTResolutionMask = mLUTResolution - 1;
401 mInvLUTResolution = 1.0f / mLUTResolution;
402 mNumTargetChannels = static_cast<uint8>(numTargetChannels);
403 }
404
405 JPL_INLINE auto LUT2D::LUTPositionToAngle(int pos) const -> float
406 {
407 const Vec2 direction = FromDiamond((static_cast<float>(pos) * static_cast<float>(mInvLUTResolution)));
408 float angle = atan2f(direction.Y, direction.X);
409 if (angle < 0.0f)
410 angle += JPL_TWO_PI;
411 return angle;
412 }
413
414 JPL_INLINE void LUT2D::GetSpeakerGains(int lutPosition, std::span<float> outGains) const
415 {
416 JPL_ASSERT(outGains.size() <= mNumTargetChannels);
417
418 const float* speakerGain = &mData[mNumTargetChannels * lutPosition];
419 std::memcpy(outGains.data(), speakerGain, sizeof(float) * outGains.size());
420 }
421
422 JPL_INLINE void LUT2D::GetSpeakerGains(const Vec2& direction, std::span<float> outGains) const
423 {
424 GetSpeakerGains(CartesianToLUTPosition(direction.X, direction.Y), outGains);
425 }
426
427 JPL_INLINE void LUT2D::GetSpeakerGains(const simd& dirX, const simd& dirY, std::span<simd> outGains) const
428 {
429 // For each speaker we compute 4 directions.
430 // We can vectorize the encoding of the direction into LUT index,
431 // however we have to retrieve the channel gains individually per direction index,
432 // since the location for each simd lane in LUT differs.
433
434 uint32 lutPositions[4];
435 (CartesianToLUTPosition(dirX, dirY) * mNumTargetChannels).store(lutPositions);
436
437 static constexpr std::size_t bufferSize = 32 * simd::size();
438 JPL_ASSERT(bufferSize >= outGains.size() * simd::size());
439
440 float buffer[bufferSize];
441 const uint32 offsets[]{
442 0,
443 mNumTargetChannels,
444 static_cast<uint32>(mNumTargetChannels) << 1,
445 (static_cast<uint32>(mNumTargetChannels) << 1) + mNumTargetChannels
446 };
447
448 // Retrieve gains from the LUT and write contiguously
449 // d1[ch1, ch2], dr2[ch1, ch2]...
450 for (uint32 i = 0, dest = 0; i < simd_mask::size(); ++i, dest += mNumTargetChannels)
451 {
452 std::memcpy(&buffer[dest], &mData[lutPositions[i]], sizeof(float) * mNumTargetChannels);
453 }
454
455 // Copy gains from the buffer strided into out simd lanes
456 for (uint32 si = 0; si < outGains.size(); ++si)
457 {
458 // dr1[ch1], dr2[ch1], dr3[ch1], dr4[ch1]
459 outGains[si] = simd(buffer[si], buffer[si + offsets[1]], buffer[si + offsets[2]], buffer[si + offsets[3]]);
460 }
461 }
462
463 inline void LUT2D::BuildDiamondToUniformIndexLUT(Array<uint32>& outCorrectionLut, uint32 N_uniform, uint32 M_corr /*= 1024*/)
464 {
465 // Require power-of-two sizes for cheap masking
466 auto isPow2 = [](uint32 n) { return n && ((n & (n - 1)) == 0); };
467 JPL_ASSERT(isPow2(N_uniform) && "N_uniform must be power-of-two");
468 JPL_ASSERT(isPow2(M_corr) && "M_corr must be power-of-two");
469
470 outCorrectionLut.resize(M_corr);
471
472 const float invM_Corr = 1.0f / M_corr;
473
474 for (uint32 j = 0; j < M_corr; ++j)
475 {
476 // diamond parameter for this table cell
477 const float p = static_cast<float>(j) * invM_Corr;
478
479 // decode to unit vector on circle
480 const Vec2 v = FromDiamond(p);
481
482 // recover true polar angle in [theta, 2PI)
483 float theta = std::atan2(v.X, v.Y);
484 if (theta < 0.0f)
485 theta += JPL_TWO_PI;
486
487 // map to our uniform-angle LUT index
488 const float t = (theta * JPL_INV_TWO_PI) * static_cast<float>(N_uniform);
489 const auto idx = static_cast<uint32>(std::llround(t)) & (N_uniform - 1);
490
491 outCorrectionLut[j] = idx;
492 }
493 }
494
495 inline void LUT2D::BuildDiamondToAngleNormLUT(Array<float>& outCorrectionLUT, uint32 M_corr)
496 {
497 auto isPow2 = [](uint32_t n) { return n && ((n & (n - 1)) == 0); };
498 JPL_ASSERT(isPow2(M_corr));
499
500 outCorrectionLUT.resize(M_corr);
501
502 const float invM_Corr = 1.0f / M_corr;
503
504 for (uint32_t j = 0; j < M_corr; ++j)
505 {
506 // diamond parameter for this table cell
507 const float p = static_cast<float>(j) * invM_Corr;
508
509 // decode to unit vector on circle
510 const Vec2 v = FromDiamond(p);
511
512 float theta = std::atan2(v.X, v.Y); // (-PI, PI]
513 if (theta < 0.0f)
514 theta += JPL_TWO_PI; // [0, 2PI)
515
516 outCorrectionLUT[j] = theta * JPL_INV_TWO_PI; // normalized angle
517 }
518 }
519
520
521#if 0
522 JPL_INLINE float CircularLerp01(float a, float b, float t)
523 {
524 // both in [0,1); treat 1 as 0 on the circle
525 float d = b - a;
526 if (d > 0.5f) d -= 1.0f;
527 if (d < -0.5f) d += 1.0f;
528 float u = a + t * d;
529 if (u < 0.0f) u += 1.0f;
530 if (u >= 1.0f) u -= 1.0f;
531 return u;
532 }
533#endif
534
535 //==========================================================================
536 template<auto GetSpeakerAngleFunction>
538 : mLUT(LUT)
539 {
540 mChannelMapTarget = channelMap;
541 mNumTargetChannels = channelMap.GetNumChannels();
542
543 // Special case for headphones profile
544 bHeadphonesProfile = channelMap.GetChannelMask() == ChannelMask::StereoHeadphones;
545
546 // We need at least quad layout for VBAP to work
547 mChannelMapInternal =
548 not bHeadphonesProfile and channelMap.GetNumChannels() < 4
550 : channelMap;
551
552 mNumInternalChannels = mChannelMapInternal.GetNumChannels();
553 mLFEIndex = mChannelMapInternal.GetChannelIndex(EChannel::LFE);
554
555 if (not bHeadphonesProfile)
556 {
557 // Extract sortet speaker angles
558 static constexpr bool skipLFE = false;
559 VBAP::ChannelAngle::GetSortedChannelAngles(mChannelMapInternal, mChannelAngels, GetSpeakerAngleFunction, skipLFE);
560 JPL_ASSERT(mChannelAngels.size() == mNumInternalChannels);
561
562 //JPL_ASSERT(intermNumChannels <= Traits::MAX_CHANNELS);
563
564 ComputePairMatrices();
565
567 {
568 mChannelConversionWeights.Resize(mNumTargetChannels, mNumInternalChannels);
569 ComputeChannelConversionRectangularWeights(mChannelMapInternal, mChannelMapTarget, mChannelConversionWeights);
570 }
571 }
572
573 mLUT.Resize(LUTType::LUTStats::Resolution, mNumTargetChannels);
574 }
575
576 template<auto GetSpeakerAngleFunction>
578 {
579 if (bHeadphonesProfile)
580 return JPL_PI;
581
582 float shortestAperture = std::numeric_limits<float>::max();
583
584 const uint32 lastSpeakerId = mChannelAngels.back().ChannelId;
585
586 auto findShortestAperture = [&](const ChannelAngle& cha1, const ChannelAngle& cha2)
587 {
588 if (cha1.ChannelId == lastSpeakerId)
589 shortestAperture = std::min(shortestAperture, JPL_TWO_PI - cha1.Angle + cha2.Angle);
590 else
591 shortestAperture = std::min(shortestAperture, cha2.Angle - cha1.Angle);
592 };
593
594 ForEachChannelAnglePair(*this, findShortestAperture);
595
596 return shortestAperture;
597 }
598
599 template<auto GetSpeakerAngleFunction>
600 inline bool LUTBuilder2D<GetSpeakerAngleFunction>::ComputeCellFor(const Vec2& direction, int lutOffset)
601 {
602 if (bHeadphonesProfile)
603 {
604 const float left = Math::Sqrt(Math::FMA(direction.X, -0.5f, 0.5f));
605 const float right = Math::Sqrt(Math::FMA(direction.X, 0.5f, 0.5f));
606 mLUT.mData[lutOffset] = left;
607 mLUT.mData[lutOffset + 1] = right;
608 return true;; // Nothing more to do here
609 }
610
611 // Assign speaker contribution values
612 for (const ChannelPair& pair : mChannelPairs)
613 {
614 Vec2 gains = pair.invL.Transform(direction);
615
616 if (gains.X < 0.0f || gains.Y < 0.0f)
617 continue; // not our pair
618
619 gains.Normalize();
620
621 if (RequiresChannelConversion())
622 {
623 StoreChannelGainsConverted(pair.ChannelId1, pair.ChannelId2, gains, lutOffset);
624 }
625 else
626 {
629 mLUT.mData[lutOffset + pair.ChannelId1] = gains.X;
630 mLUT.mData[lutOffset + pair.ChannelId2] = gains.Y;
631 }
632
633 return true;
634 }
635
636 // This should be unreachable
637 JPL_ASSERT(false);
638 return false;
639 }
640
641 template<auto GetSpeakerAngleFunction>
643 {
644 bool bAnyFailed = false;
645
646 // Build a LUT with uniform diamond steps
647 const float step = 1.0f / mLUT.GetLUTResolution();
648 float diamond = 0.0f;
649
650 for (uint32 pos = 0; pos < mLUT.GetLUTResolution(); ++pos, diamond += step)
651 {
652 JPL_ASSERT(diamond <= 1.0f);
653
654 const Vec2 direction = FromDiamond(diamond);
655
656 // Position of the next diamond step value in the LUT
657 // (number of channels stride)
658 const uint32 offset = mNumTargetChannels * pos;
659
660 // TODO: we may or may not want to terminate if any cell fails
661 bAnyFailed |= ComputeCellFor(direction, offset);
662 }
663
664 return bAnyFailed;
665 }
666
667 template<auto GetSpeakerAngleFunction>
669 {
670 if (not JPL_ENSURE(not bHeadphonesProfile))
671 return; // This shouldn't be called for headphones profile
672
673 auto makeInvMat = [&](const ChannelAngle& cha1, const ChannelAngle& cha2)
674 {
675 //JPL_ASSERT(Math::IsPositiveAndBelow(cha2.Angle - cha1.Angle, JPL_PI + 1e-6f));
676
677 const auto [s1, c1] = Math::SinCos(cha1.Angle);
678 const auto [s2, c2] = Math::SinCos(cha2.Angle);
679
680 const auto mat =
682 Vec2(s1, -c1),
683 Vec2(s2, -c2));
684
685 // Inverse will fail for wide aperture angles,
686 // therefore we need at least quad layout for <= 90 degree max aperture
687 Math::Mat2<Vec2> invL;
688 (void)JPL_ENSURE(mat.TryInverse(invL));
689
690 mChannelPairs.emplace_back(
691 invL,
692 cha1.ChannelId,
693 cha2.ChannelId);
694 };
695
696 ForEachChannelAnglePair(*this, makeInvMat);
697 }
698
699 template<auto GetSpeakerAngleFunction>
700 template<class ThisType, class CallbackType>
701 inline void LUTBuilder2D<GetSpeakerAngleFunction>::ForEachChannelAnglePair(ThisType& self, CallbackType&& callback)
702 {
703 if (not JPL_ENSURE(not self.bHeadphonesProfile))
704 return; // This shouldn't be called for headphones profile
705
706 auto sanitizeLFEIndex = [&self](uint32 idx)
707 {
708 return idx + (self.mChannelAngels[idx].ChannelId == self.mLFEIndex);
709 };
710
711 for (uint32 ch = 0; ch < self.mChannelAngels.size() - 1; ++ch)
712 {
713 const uint32 ch1 = sanitizeLFEIndex(ch);
714 const uint32 ch2 = sanitizeLFEIndex(ch1 + 1);
715
716 const ChannelAngle& cha1 = self.mChannelAngels[ch1];
717 const ChannelAngle& cha2 = self.mChannelAngels[ch2];
718 callback(cha1, cha2);
719 }
720
721 // Handle wrap around
722 {
723 const uint32 ch1 = static_cast<uint32>(self.mChannelAngels.size()) - 1;
724 const uint32 ch2 = sanitizeLFEIndex(0);
725 const ChannelAngle& cha1 = self.mChannelAngels[ch1];
726 const ChannelAngle& cha2 = self.mChannelAngels[ch2];
727 callback(cha1, cha2);
728 }
729 }
730
731 template<auto GetSpeakerAngleFunction>
732 JPL_INLINE void LUTBuilder2D<GetSpeakerAngleFunction>::StoreChannelGainsConverted(uint32 channelId1,
733 uint32 channelId2,
734 const Vec2& gains,
735 uint32 lutOffset)
736 {
737 std::span<float> targetGains(&mLUT.mData[lutOffset], mNumTargetChannels);
738
739 // Convert from intermediary to target
740 // channel map that we store in the LUT
741 ApplyChannelConversion({ channelId1, channelId2 }, gains, targetGains);
742
743 // Normalize
745 Algo::NormalizeL2(targetGains);
746 }
747
748 template<auto GetSpeakerAngleFunction>
749 inline void LUTBuilder2D<GetSpeakerAngleFunction>::ApplyChannelConversion(const std::pair<uint32, uint32>& inChannelIds,
750 const Vec2& inGains,
751 std::span<float> outValues) const
752 {
753 for (uint32 iChannelOut = 0; iChannelOut < outValues.size(); ++iChannelOut)
754 {
755 const float accumulation =
756 inGains.X * mChannelConversionWeights[iChannelOut][inChannelIds.first] +
757 inGains.Y * mChannelConversionWeights[iChannelOut][inChannelIds.second];
758
759 outValues[iChannelOut] = accumulation;
760 }
761 }
762
763#if JPL_VALIDATE_VBAP_LUT
764 template<auto GetSpeakerAngleFunction>
765 void LUTBuilder2D<GetSpeakerAngleFunction>::ValidateLUT() const
766 {
767
768#if 0 // TODO: implement for 2D
769
770 JPL_ASSERT(mLUT != nullptr);
771
772 for (uint32 i = 0; i < mLUT->Speakers.size(); ++i)
773 {
774 if (!LUTCodec::IsValidCode(i))
775 continue;
776
777 const auto& speakers = mLUT->Speakers[i];
778 JPL_ASSERT(speakers[0] != speakers[1]);
779 JPL_ASSERT(speakers[1] != speakers[2]);
780 JPL_ASSERT(speakers[2] != speakers[0]);
781 }
782
783 for (uint32 i = 0; i < mLUT->Gains.size(); ++i)
784 {
785 if (!LUTCodec::IsValidCode(i))
786 continue;
787
788 const auto& gains = mLUT->Gains[i];
789 // Gains can be encoded in 24 or even 16 bit
790 const std::array<float, 3> gainsDecoded
791 {
792 gains[0],
793 gains[1],
794 gains[2]
795 };
796 JPL_ASSERT(Algo::IsNormalizedL2(gainsDecoded));
797 }
798#endif
799 }
800#endif
801
802} // 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:156
constexpr uint32 GetNumChannels() const noexcept
Definition ChannelMap.h:168
static constexpr ChannelMap FromChannelMask(uint32 channelMask)
Definition ChannelMap.h:211
constexpr uint32 GetChannelMask() const
Definition ChannelMap.h:207
constexpr uint32 GetChannelIndex(EChannel channel) const
Definition ChannelMap.h:176
Look-up table containing channel gains for each direction.
Definition VBAPLUT2D.h:56
JPL_INLINE int AngleNormalizedToLUTPosition(float angleNormalised) const
Definition VBAPLUT2D.h:329
JPL_INLINE bool IsInitialized() const noexcept
Definition VBAPLUT2D.h:107
static void BuildDiamondToAngleNormLUT(Array< float > &outCorrectionLUT, uint32 M_corr=1024)
Definition VBAPLUT2D.h:495
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:347
static void BuildDiamondToUniformIndexLUT(Array< uint32 > &outCorrectionLUT, uint32 N_uniform, uint32 M_corr=1024)
Definition VBAPLUT2D.h:463
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:339
JPL_INLINE float LUTPositionToAngle(int pos) const
Definition VBAPLUT2D.h:405
JPL_INLINE void GetSpeakerGains(int lutPosition, std::span< float > outGains) const
Get preprocessed speaker gains at specific LUT posotion.
Definition VBAPLUT2D.h:414
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:642
LUTBuilder2D(ChannelMap channelMap, LUTType &LUT)
Definition VBAPLUT2D.h:537
bool ComputeCellFor(const Vec2 &direction, int lutOffset)
Comput LUT gains for given direction and LUT offset.
Definition VBAPLUT2D.h:600
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:577
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 StereoHeadphones
Definition ChannelMap.h:104
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 constexpr T Sqrt(T x) noexcept
Definition Math.h:269
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