JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
VBAPLUT3D.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"
31
32#include <array>
33#include <cstring>
34#include <vector>
35#include <span>
36#include <concepts>
37#include <type_traits>
38#include <algorithm>
39#include <memory>
40
41#define JPL_DBG_DUMP_SPEAKER_FAILED_SELECTION 0
42
43#if JPL_DBG_DUMP_SPEAKER_FAILED_SELECTION
44#include <format>
45#include <string>
46#include <sstream>
47#endif
48
49namespace JPL::VBAP
50{
52 template<class T>
53 concept CLUT = requires { typename T::GainType; }&& requires { typename T::SpeakerIndexType; };
54
55 //==========================================================================
57 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
58 class LUTBuilder;
59
60 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
61 class LUTQuery;
62
63 //==========================================================================
66 template<auto GetSpeakerVectorFunction, class LUTCodec, CLUT LUTType>
68 {
69 public:
70 using Vec3Type = std::remove_cvref_t<decltype(GetSpeakerVectorFunction(EChannel{}))>;
71
74
75 //======================================================================
81
83 [[nodiscard]] static JPL_INLINE QueryType Query(const LUTType& LUT)
84 {
85 return QueryType(LUT);
86 }
87 };
88
89 //==========================================================================
93 enum class ELUTSize
94 {
95 KB_983, // No gain compression, direct speaker indices, no dynamic data
96 KB_851, // No gain compression, speaker tiplet indices, dynamic data
97 KB_786, // 24-bit gain compression, direct speaker indices, no dynamic data
98 KB_655, // 24-bit gain compression, speaker triplet indices, dynamic data
99
100 // For this type gains have to be computed on query, by a single matrix multiplication
101 // -----------------------------------------------------------------
102 KB_65 // No precomputed gains, speaker triplet indices, dynamic data with inv matrices
103 };
104
105 template<class T>
106 concept CLUTType = std::same_as<T, ELUTSize>;
107
111 template<CLUTType auto T, size_t N, CVec3 Vec3Type = void*>
112 struct LUT;
113
114 //==========================================================================
116 template<class T>
117 using GainPack = std::array<T, 3>;
118
120 using GainTypeNone = void*;
121
124
126 using SpeakerTripletIdx = std::array<SpeakerIdx, 3>;
127
131
132 //==========================================================================
136 {
138 uint8 DummyIndex; // Index where the dummy is in a triplet (index 3 if none)
139 };
140
141 template<CVec3 Vec3Type>
143 {
144 static_assert(!std::same_as<Vec3Type, void*> && "Vec3Type is not specified for the LUT that requires it.");
145
147 };
148
149 using DynamicDataTypeNone = void*;
150
151 template<class DynamicDataType_>
153 {
155 std::pmr::vector<DynamicDataType> Data{ GetDefaultMemoryResource() };
156 };
157
158 template<>
160
161 //==========================================================================
163 template<size_t N, class GainType_, class SpeakerIndexType_, class DynamicDataType_ = DynamicDataTypeNone>
164 struct LUTBase : DynamicDataTrait<DynamicDataType_>
165 {
168
169 std::array<GainPack<GainType>, N> Gains; // Speaker gains for each LUT index
170 std::array<SpeakerIndexType, N> Speakers; // Indices of the speakers to apply gains to
171 };
172
174 template<size_t N, class SpeakerIndexType_, class DynamicDataType_>
176 {
179
180 std::array<SpeakerIndexType, N> Speakers; // Indices of the speakers to compute gains for
181 };
182
183 //==========================================================================
185
186 template<size_t N, CVec3 Vec3Type>
187 struct LUT<ELUTSize::KB_983, N, Vec3Type>
188 : LUTBase<N, float, SpeakerTripletIdx> {};
189
190 template<size_t N, CVec3 Vec3Type>
191 struct LUT<ELUTSize::KB_851, N, Vec3Type>
192 : LUTBase<N, float, TripletIdx, DynamicDataTri> {};
193
194 template<size_t N, CVec3 Vec3Type>
195 struct LUT<ELUTSize::KB_786, N, Vec3Type>
196 : LUTBase<N, Gain24Bit, SpeakerTripletIdx> {};
197
198 template<size_t N, CVec3 Vec3Type>
199 struct LUT<ELUTSize::KB_655, N, Vec3Type>
200 : LUTBase<N, Gain24Bit, TripletIdx, DynamicDataTri> {};
201
202 template<size_t N, CVec3 Vec3Type>
203 struct LUT<ELUTSize::KB_65, N, Vec3Type>
204 : LUTBase<N, GainTypeNone, TripletIdx, DynamicDataWithMat<Vec3Type>> {};
205
206#if 0 // Note: 16-bit gains may be too noisy below -60 dB
207 template<size_t N, CVec3 Vec3Type> struct LUT<ELUTSize::KB_589, N, Vec3Type> : LUTBase<N, Gain16Bit, SpeakerTripletIdx> {};
208 template<size_t N, CVec3 Vec3Type> struct LUT<ELUTSize::KB_458, N, Vec3Type> : LUTBase<N, Gain16Bit, TripletIdx, DynamicDataTri> {};
209#endif
210
211 //==========================================================================
213
214 template<size_t N>
216
217 template<size_t N>
219
220 template<size_t N>
222
223 template<size_t N>
225
226 template<size_t N, CVec3 Vec3Type>
228
229 //==========================================================================
231 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
233 {
234 public:
235 //==========================================================================
238 static constexpr bool bLUTHasGains = !std::same_as<typename LUTType::GainType, GainTypeNone>;
239
240 //==========================================================================
247
248 JPL_INLINE explicit LUTQuery(const LUTType& lut) noexcept : LUT(lut) {}
249
252 template<CVec3 Vec3Type>
253 void GainsFor(const Vec3Type& direction, VBAPCell& outSpeakerGains) const;
254
255 inline void GainsFor(const simd& dirX, const simd& dirY, const simd& dirZ,
256 std::array<SpeakerIdx, 3 * simd::size()>& outIndices,
257 std::array<float, 3 * simd::size()>& outGains) const;
258
262 template<CVec3 Vec3Type>
263 JPL_INLINE void GainsFor(const Vec3Type& direction, std::span<float> outGains) const;
264
265 private:
266 JPL_INLINE void ExtractGains(int index, std::span<float, 3> outGains) const requires (bLUTHasGains);
267
268 // If LUT doen't have precomputed gains, we have to do things manually
269 template<CVec3 Vec3Type>
270 JPL_INLINE void ExtractGains(int index, const Vec3Type& direction, std::span<float, 3> outGains) const requires (!bLUTHasGains);
271
272 public:
273 const LUTType& LUT;
274 };
275
276 //==========================================================================
278 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
280 {
281 public:
282 static constexpr bool cLUTHasDynamicData = requires { typename LUTType::DynamicDataType; };
283 public:
285
287 [[nodiscard]] JPL_INLINE uint32 GetNumRealChannels() const noexcept { return static_cast<uint32>(mVectors.size()) - mDummySpeakers.GetNumDummies(); }
288 [[nodiscard]] JPL_INLINE const std::pmr::vector<SpeakerTripletIdx>& GetTris() const noexcept { return mTris; }
289 [[nodiscard]] float FindShortestAperture() const;
290
291 // Handle indices >= LFE index, which we don't use internally for trianglation
294
300 [[nodiscard]] bool ComputeCellFor(const Vec3Type& direction, int lutIndex);
301
304
305#if JPL_VALIDATE_VBAP_LUT
306 void ValidateLUT() const;
307#endif
308
309 private:
310#if 0 // W0 may need 2D matrices in the future
312#endif
313 JPL_INLINE bool Triangulate();
314 JPL_INLINE void ComputeTriMatrices();
315
316 void ExtractDynamicData() const requires(cLUTHasDynamicData);
317 JPL_INLINE uint32 FindReaplacementForDummy(const SpeakerTriangulation::Vec3i& tri) const;
318 private:
319 LUTType& mLUT;
320
321 std::pmr::vector<Vec3Type> mVectors; // speaker direction vectors
322
324
325 uint32 mNumRealSpeakers = 0;
326 uint32 mNumGroundSpeakers = 0;
327 uint32 mNumTopSpeakers = 0;
328
329 // Potential dynamic data stored in some LUT types
330 std::pmr::vector<SpeakerTripletIdx> mTris;
331 std::pmr::vector<Math::Mat3<Vec3Type>> mTrisInvMats;
332 uint32 mLFEIndex = ChannelMap::InvalidChannelIndex;
333 };
334} // namespace JPL::VBAP
335
336//==============================================================================
337//
338// Code beyond this point is implementation detail...
339//
340//==============================================================================
341
342// It may or may not sound more natural to normalize speaker
343// triplet gains including "dummy" speaker, which would result
344// in slight dip in volume in the lobe where "dummy" is "active".
345//? Note: if we normalize with dummy, we won't be able to obtain
346//? overall consistent gain normalization of the entire output of the panning
347#define JPL_NORMALIZE_GAINS_WITH_DUMMY 0
348
349namespace JPL::VBAP
350{
351 //==========================================================================
352 template<auto GetSpeakerVectorFunction, class LUTCodec , CVec3 Vec3Type, CLUT LUTType>
354 : mLUT(outLUT)
355 , mVectors(GetDefaultMemoryResource())
356 , mDummySpeakers(channelMap, mVectors)
357 , mTris(GetDefaultMemoryResource())
358 , mTrisInvMats(GetDefaultMemoryResource())
359 {
360 channelMap.ForEachChannel([this](EChannel channel, uint32 index)
361 {
362 if (channel == EChannel::LFE)
363 {
364 mLFEIndex = index;
365 }
366 else
367 {
368 mVectors.push_back(GetSpeakerVectorFunction(channel));
369
370 mNumGroundSpeakers += channel < EChannel::TOP_Channels;
371 mNumTopSpeakers += channel >= EChannel::TOP_Channels;
372 }
373 });
374
375 // We have to use "dummy" speakers to calculate proper gains
376 // when we don't have speakers all around the listener.
377 // These dummy speakeres can be discarded after building the LUT.
378 //
379 // For symetrical layout, non-side planes that don't have
380 // center channel (top plane, back plane, bottom plane),
381 // are triangulated non-symetrically, which results in
382 // non-symetrical gain distribution for some symetrical directions.
383
384 // Since at the moment ChannelMap doesn't support speakers on the bottom,
385 // and most of the common layouts don't have speaker below listening plane,
386 // we at least need to add a dummy speaker there for a better topology
387 // of the convex hull.
388 mDummySpeakers.AddDummy(Vec3Type(0.0f, -1.0f, 0.0f));
389
390 // We have to accept potential asymetry of the back face,
391 // to avoid making holes in panning where there's only
392 // one real speaker per triangle (i.e the other 2 are dummies).
393
394 // We can at least ensure symmetrical topology on the top
395 if (mNumTopSpeakers == 6 || mNumTopSpeakers == 4)
396 {
397 mDummySpeakers.AddIfChannelNotPresent(EChannel::TopCenter);
398 }
399 else
400 {
401 JPL_ASSERT(mNumTopSpeakers == 2);
402 }
403
404 mNumRealSpeakers = static_cast<uint32>(mVectors.size() - GetNumDummies());
405
406#if 0
408#endif
409
410 // Triangulate and compute matrices
411 if (Triangulate())
412 {
413 if constexpr (cLUTHasDynamicData)
414 {
415 ExtractDynamicData();
416 }
417 }
418 else
419 {
420 JPL_ASSERT(false, "Failed to triangulate speaker setup.");
421 }
422
423 }
424
425#if 0
426 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
428 {
429 const ChannelMap channelMap = mDummySpeakers.GetChannelMap();
430
431 // Sort channels to ensure we get valid edges form consecutive indices
432 // (this will skip top channels, which is what we need)
433 using ChannelAngleArray = typename Traits::template Array<ChannelAngle<Traits>>;
434 ChannelAngleArray sourceChannelsSorted;
436
437 JPL_ASSERT(sourceChannelsSorted.size() >= mNumGroundSpeakers);
438
439 auto createEdgeMat = [this](uint32 a, uint32 b)
440 {
441 const Vec2 A{ mVectors[a].X, mVectors[aGetZ(]) };
442 const Vec2 B{ mVectors[b].X, mVectors[bGetZ(]) };
443 Math::Mat2<Vec2> L{ A, B };
444
445 auto& Linv = m2DMats.emplace_back();
446 JPL_ENSURE(L.TryInverse(Linv));
447 };
448
449 // TODO: this ChannelId may be incorrect (relative to our vectors) past LFE, since it represent index in channel map including LFE
450
451 // Start with wrap around (last->first)
452 uint32 firstChannelIdx = sourceChannelsSorted[mNumGroundSpeakers - 1].ChannelId;
453
454 // Ground speakers must be laid out first in mVectors
455 for (uint32 i = 0; i < sourceChannelsSorted.size() && i < mNumGroundSpeakers; ++i)
456 {
460 }
461 }
462#endif
463
464 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
465 JPL_INLINE bool LUTBuilder<GetSpeakerVectorFunction, LUTCodec, Vec3Type, LUTType>::Triangulate()
466 {
467 if (SpeakerTriangulation::TriangulateSpeakerLayout(std::span<const Vec3Type>(mVectors), mTris))
468 {
469 ComputeTriMatrices();
470 return true;
471 }
472 return false;
473 }
474
475 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
476 JPL_INLINE void LUTBuilder<GetSpeakerVectorFunction, LUTCodec, Vec3Type, LUTType>::ComputeTriMatrices()
477 {
478 mTrisInvMats.clear();
479 mTrisInvMats.reserve(mTris.size());
480 for (const SpeakerTriangulation::Vec3i& tri : mTris)
481 {
482 Math::Mat3<Vec3Type> L{ mVectors[tri[0]], mVectors[tri[1]], mVectors[tri[2]] };
483 Math::Mat3<Vec3Type>& Linv = mTrisInvMats.emplace_back();
484 (void)JPL_ENSURE(L.TryInverse(Linv));
485 }
486 }
487
488 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
490 {
491 float maxDot = -std::numeric_limits<float>::max();
492 for (const SpeakerTriangulation::Vec3i& tri : mTris)
493 {
494 const Vec3Type& A = mVectors[tri[0]];
495 const Vec3Type& B = mVectors[tri[1]];
496 const Vec3Type& C = mVectors[tri[2]];
497
498 maxDot = std::max({ maxDot, DotProduct(A, B), DotProduct(B, C), DotProduct(C, A) });
499 }
500 JPL_ASSERT(maxDot > -std::numeric_limits<float>::max());
501 return maxDot;
502 }
503
504 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
506 {
507 return SpeakerTripletIdx{
508 SanitizeSpeakerIndex(triplet[0]),
509 SanitizeSpeakerIndex(triplet[1]),
510 SanitizeSpeakerIndex(triplet[2])
511 };
512 }
513
514 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
519
520 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
522 {
523 using LUTGainType = typename LUTType::GainType;
524 using LUTSpeakerIdxType = typename LUTType::SpeakerIndexType;
525 static constexpr bool bLUTHasGains = !std::same_as<LUTGainType, GainTypeNone>;
526 static constexpr bool bLUTStoresTripletIdx = std::same_as<LUTSpeakerIdxType, TripletIdx>;
527
528 Vec3Type directionSafe = direction;
529
530 // TODO: if direction falls directly onto a dummy speaker, we might need to shift it over a bit
531 // otherwise we might get weirdness where at there're a hole in just one spot
532 // without fade if we normalize after dummy.
533 /* if (mDummySpeakers.HasDummyAt(direction, static_cast<float>(LUTCodec::cMaxVectorError)))
534 {
535 static constexpr float offset =
536 static_cast<float>(LUTCodec::cMaxVectorError) * 2.0f;
537
538 GetZ(directionSafe) += offset;
539 Normalize(directionSafe);
540 }
541 JPL_ASSERT(!mDummySpeakers.HasDummyAt(directionSafe, static_cast<float>(LUTCodec::cMaxVectorError)));*/
542
543 // Find speaker triplet for our direction,
544 // the one that has all gains positive
545 for (int triI = 0; triI < mTrisInvMats.size(); ++triI)
546 {
547 Vec3Type gains = mTrisInvMats[triI].Transform(directionSafe);
548
549 static constexpr float eps = -JPL_FLOAT_EPS;
550 if (GetX(gains) < eps || GetY(gains) < eps || GetZ(gains) < eps)
551 continue; // direction is outside of this trignale
552
553 if (!JPL_ENSURE(!Math::IsNearlyZero(LengthSquared(gains))))
554 {
555 // TODO: Loudspeaker vectors are collinear, a case for 1D panning
556 // (maybe if we don't find proper triplet, fall back to best collinear case and compute 1D gains)
557 continue;
558 }
559#if JPL_NORMALIZE_GAINS_WITH_DUMMY
560 // Normalize before zeroing out "dummy" speaker
561 Normalize(gains);
562#endif
563
565
566#if defined(JPL_ENABLE_ASSERTS)
567 // Ensure we don't create triangles with > 1 dummy speaker
568 {
569 [[maybe_unused]] int numDummies = 0;
570 for (const uint32 idx : tri)
571 numDummies += mDummySpeakers.Contains(idx);
573 }
574#endif
575
576 // If we have precalculated gains, one of the seakers may be a dummy.
577 // We need to silence it and renormalize the gains.
578 if constexpr (bLUTHasGains)
579 {
580 int dummy = -1;
581
582 if (mDummySpeakers.Contains(tri[0]))
583 {
584 dummy = 0;
585 SetX(gains, 0.0f);
586 }
587 else if (mDummySpeakers.Contains(tri[1]))
588 {
589 dummy = 1;
590 SetY(gains, 0.0f);
591 }
592 else if (mDummySpeakers.Contains(tri[2]))
593 {
594 dummy = 2;
595 SetZ(gains, 0.0f);
596 }
597
598 // If our tri contains a dummy, we need to find
599 // a different speaker to assign 0 gain to in our table
600 if (dummy >= 0)
601 tri[dummy] = FindReaplacementForDummy(tri);
602 }
603
604#if !JPL_NORMALIZE_GAINS_WITH_DUMMY
605 // Normalize after "dummy" speaker is zeroed out to avoid making a "hole"
606 Normalize(gains);
607
608 //JPL_ASSERT(!Math::HasNans(gains));
609 //JPL_ASSERT(!Math::IsNearlyZero(gains.LengthSquared()));
610#endif
611
612 if constexpr (bLUTHasGains)
613 {
615 }
616
617 if constexpr (bLUTStoresTripletIdx)
618 {
619 mLUT.Speakers[lutIndex] = SanitizeSpeakerIndex(triI);
620 }
621 else
622 {
623 mLUT.Speakers[lutIndex] = {
624 SanitizeSpeakerIndex(static_cast<SpeakerIdx>(tri[0])),
625 SanitizeSpeakerIndex(static_cast<SpeakerIdx>(tri[1])),
626 SanitizeSpeakerIndex(static_cast<SpeakerIdx>(tri[2]))
627 };
628 }
629
630 return true;
631 }
632
633#if JPL_DBG_DUMP_SPEAKER_FAILED_SELECTION
634 std::stringstream ss;
635 for (int triI = 0; triI < mTrisInvMats.size(); ++triI)
636 {
637 ss << "triI " << triI << '\n';
638
639 const auto& tri = mTris[triI];
640
641 const auto& A = mVectors[tri[0]];
642 ss << "A: " << GetX(A) << " " << GetY(A) << " " << GetZ(A) << '\n';
643
644 const auto& B = mVectors[tri[1]];
645 ss << "B: " << GetX(B) << " " << GetY(B) << " " << GetZ(B) << '\n';
646
647 const auto& C = mVectors[tri[2]];
648 ss << "C: " << GetX(C) << " " << GetY(C) << " " << GetZ(C) << '\n';
649
650 Vec3Type gains = mTrisInvMats[triI].Transform(directionSafe);
651
652 ss << "gains: " << GetX(gains) << " " << GetY(gains) << " " << GetZ(gains) << '\n';
653
654 ss << "-----\n";
655 }
656 std::string dump = ss.str();
657
658 const auto formatString = std::format("Computing VBAP LUT failed. Direction {{{}, {}, {}}}, LUT index {}, InvMatsSize {}"
659 "\n Triplets Dump:\n {}",
661 lutIndex,
662 mTrisInvMats.size(),
663 dump.c_str());
664 JPL_ASSERT(false, formatString.c_str());
665#else
666 // Should be unreachable
667 JPL_ASSERT(false, "Computing VBAP LUT failed.");
668#endif
669
670 if constexpr (bLUTHasGains)
671 {
672 mLUT.Gains[lutIndex] = { LUTGainType(0.0f), LUTGainType(0.0f), LUTGainType(0.0f) };
673 }
674
675 if constexpr (bLUTStoresTripletIdx)
676 {
677 mLUT.Speakers[lutIndex] = std::numeric_limits<SpeakerIdx>::max();
678 }
679 else
680 {
681 mLUT.Speakers[lutIndex] = {
682 std::numeric_limits<SpeakerIdx>::max(),
683 std::numeric_limits<SpeakerIdx>::max(),
684 std::numeric_limits<SpeakerIdx>::max()
685 };
686 }
687
688 return false;
689 }
690
691 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
693 {
694 bool bAnyFailed = false;
695
696 //const auto yminuscode = LUTCodec::Encode(Vec3Type(0, -1, 0));
697
698 // Compute LUT values for every possible direction
699 for (uint16_t dy = 0; dy < LUTCodec::cAxisRange; ++dy)
700 {
701 for (uint16_t dx = 0; dx < LUTCodec::cAxisRange; ++dx)
702 {
703 if (!LUTCodec::AreValidComponents(dx, dy))
704 continue; // skip padded cells that have no direction
705
706 const uint32_t code = LUTCodec::CombineComponents(dx, dy);
707 const Vec3Type dir = LUTCodec::template Decode<Vec3Type>(code);
708
709 // TODO: we may or may not want to terminate if any fails
710 bAnyFailed |= ComputeCellFor(dir, code);
711 }
712 }
713
714 return bAnyFailed;
715 }
716
717#if JPL_VALIDATE_VBAP_LUT
718 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
720 {
721 for (uint32 i = 0; i < mLUT.Speakers.size(); ++i)
722 {
723 if (!LUTCodec::IsValidCode(i))
724 continue;
725
726 if constexpr (std::same_as<typename LUTType::SpeakerIndexType, SpeakerTripletIdx>)
727 {
728 const auto& speakers = mLUT.Speakers[i];
729 JPL_ASSERT(speakers[0] != speakers[1]);
730 JPL_ASSERT(speakers[1] != speakers[2]);
731 JPL_ASSERT(speakers[2] != speakers[0]);
732 }
733 else
734 {
735 // Extract speakers.
736 // At this poitn LUT should have dynamic data
737 // with speaker triplet mappings
738 const TripletIdx tripletIdx = mLUT.Speakers[i];
739 const SpeakerTripletIdx& speakers = mLUT.Data[tripletIdx].Tri;
740
741 JPL_ASSERT(speakers[0] != speakers[1]);
742 JPL_ASSERT(speakers[1] != speakers[2]);
743 JPL_ASSERT(speakers[2] != speakers[0]);
744 }
745 }
746
747 if constexpr (requires{ mLUT.Gains; })
748 {
749 for (uint32 i = 0; i < mLUT.Gains.size(); ++i)
750 {
751 if (!LUTCodec::IsValidCode(i))
752 continue;
753
754 const auto& gains = mLUT.Gains[i];
755 // Gains can be encoded in 24 or even 16 bit
756 const std::array<float, 3> gainsDecoded
757 {
758 gains[0],
759 gains[1],
760 gains[2]
761 };
763 }
764 }
765 }
766#endif
767
768 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
769 inline void LUTBuilder<GetSpeakerVectorFunction, LUTCodec, Vec3Type, LUTType>::ExtractDynamicData() const requires(cLUTHasDynamicData)
770 {
771 mLUT.Data.resize(mTris.size());
772
773 for (uint32 i = 0; i < mLUT.Data.size(); ++i)
774 {
775 auto& data = mLUT.Data[i];
776
777 if constexpr (requires{ data.Tri; })
778 {
779 static_assert(requires{ data.DummyIndex; });
780
781 // Copy index of the speaker triplet
782 data.Tri = mTris[i];
783
784 // Find and replace dummy speaker with a real one if needed
785 for (; data.DummyIndex < 3; ++data.DummyIndex)
786 {
787 if (mDummySpeakers.Contains(data.Tri[data.DummyIndex]))
788 {
789 data.Tri[data.DummyIndex] = FindReaplacementForDummy(data.Tri);
790 break;
791 }
792 }
793 }
794
795 // Copy inverse matrix for the gain calculation done for the LUT
796 // that doesn't store precomputed gains, only selected speaker triplet
797 if constexpr (requires{ data.TrisInvMat; })
798 data.TrisInvMat = mTrisInvMats[i];
799 }
800 }
801
802 template<auto GetSpeakerVectorFunction, class LUTCodec, CVec3 Vec3Type, CLUT LUTType>
803 JPL_INLINE uint32 LUTBuilder<GetSpeakerVectorFunction, LUTCodec, Vec3Type, LUTType>::FindReaplacementForDummy(const SpeakerTriangulation::Vec3i& tri) const
804 {
805 // Simply increment index until we find one not already in the 'tri',
806 // It doesn't matter which speaker it is, since its gain in this tri is going to be 0
807 uint32 newI = 0;
808 while (std::ranges::find(tri, newI) != std::ranges::end(tri))
809 ++newI;
810
811 // Should be impossible
812 JPL_ASSERT(newI < mNumRealSpeakers);
813
814 return newI;
815 }
816
817 //==========================================================================
818 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
819 template<CVec3 Vec3Type>
821 {
824
825 // Convert direction to LUT index
826 //const auto index = GetIndexFromDirection(direction);
827 const auto index = LUTCodec::Encode(direction);
828
829 if constexpr (std::same_as<typename LUTType::SpeakerIndexType, SpeakerTripletIdx>)
830 {
831 outSpeakers = LUT.Speakers[index];
832 ExtractGains(index, outGains);
833 }
834 else
835 {
836 // Extract speakers
837 const TripletIdx tripletIdx = LUT.Speakers[index];
838 outSpeakers = LUT.Data[tripletIdx].Tri;
839
840 // Extract gains
841 std::array<float, 4> gains{ 0.0f, 0.0f, 0.0f, 0.0f };
842
843 if constexpr (bLUTHasGains)
844 ExtractGains(index, std::span<float, 3>(gains.data(), 3));
845 else
846 ExtractGains(index, direction, std::span<float, 3>(gains.data(), 3));
847
848 // If one of the speakedrs is dummy, we need to silence it
849 const uint8 dummyIndex = LUT.Data[tripletIdx].DummyIndex;
850
851 if constexpr (bLUTHasGains)
852 {
853 gains[dummyIndex] = 0.0f;
854 }
855 else
856 {
857#if !JPL_NORMALIZE_GAINS_WITH_DUMMY
858 // If the direction falls directly onto a dummy,
859 // we just assign the gains to the other speakers
860 // to ensure consistent output
862 {
863 gains[0] = 1.0f;
864 gains[1] = 1.0f;
865 gains[2] = 1.0f;
866 }
867 // Now it's safe to silence the dummy
868 gains[dummyIndex] = 0.0f;
869
870 // If we just computed gains, we need to normalize
871 // here after silencing the dummy.
873
874 //JPL_ASSERT(!Math::HasNans(Vec3Type(gains[0], gains[1], gains[2])));
875#else
876 gains[dummyIndex] = 0.0f;
877#endif
878 }
879
880 // Copy valid speaker gains to the output
881 std::memcpy(outGains.data(), gains.data(), sizeof(float) * 3);
882
884 }
885 }
886
887 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
889 std::array<SpeakerIdx, 3 * simd::size()>& outSpeakersIndices,
890 std::array<float, 3 * simd::size()>& outSpeakerGains) const
891 {
892 // Convert directions to LUT indices
893 const simd_mask indexPack = LUTCodec::Encode(dirX, dirY, dirZ);
894 std::array<uint32, 4> indices;
895 indexPack.store(indices.data());
896
897 // For the vectorized GainsFor we can only vectorize the encoding of the direction into LUT indices
898 // the retrieval of the gains from the LUT cannot be vectorized, since the location of gains
899 // for each simd lane differs.
900
901 if constexpr (std::same_as<typename LUTType::SpeakerIndexType, SpeakerTripletIdx>)
902 {
903 for (uint32 i = 0, iout = 0; i < indices.size(); ++i, iout += 3)
904 {
905 const uint32 index = indices[i];
906 std::memcpy(&outSpeakersIndices[iout], &LUT.Speakers[index], sizeof(SpeakerIdx) * 3);
907 ExtractGains(index, std::span<float, 3>(&outSpeakerGains[iout], 3));
908 }
909 }
910 else
911 {
912 if constexpr (bLUTHasGains)
913 {
914 // For the LUT that has gains precomputed, we just need to retrieve them
915 // and copy to the output with an offset
916
917 for (uint32 i = 0, iout = 0; i < indices.size(); ++i, iout += 3)
918 {
919 // Extract speakers
920 const uint32 index = indices[i];
921 const TripletIdx tripletIdx = LUT.Speakers[index];
922 std::memcpy(&outSpeakersIndices[iout], &LUT.Data[tripletIdx].Tri, sizeof(SpeakerIdx) * 3);
923
924 // Extract gains
925 std::array<float, 4> gains{ 0.0f, 0.0f, 0.0f, 0.0f };
926 ExtractGains(index, std::span<float, 3>(gains.data(), 3));
927
928 // If one of the speakedrs is dummy, we need to silence it
929 const uint8 dummyIndex = LUT.Data[tripletIdx].DummyIndex;
930 gains[dummyIndex] = 0.0f;
931
932 // Copy valid speaker gains to the output
933 std::memcpy(&outSpeakerGains[iout], gains.data(), sizeof(float) * 3);
934
935 JPL_ASSERT(Algo::IsNormalizedL2(std::span<float>(&outSpeakerGains[iout], 3)));
936 }
937 }
938 else // For the LUT that doesn't hold gains, we need to process each direction
939 {
940 // Unpack the directions
941 std::array<Vec3TypeImplicit, 4> directions;
942 float xs[simd::size()]{}; dirX.store(xs);
943 float ys[simd::size()]{}; dirY.store(ys);
944 float zs[simd::size()]{}; dirZ.store(zs);
945 for (uint32 i = 0; i < simd::size(); ++i)
946 {
949 }
950
951 // Process each individual direction
952 for (uint32 i = 0, iout = 0; i < indices.size(); ++i, iout += 3)
953 {
954 const uint32 index = indices[i];
956
957 // Extract speakers
958 const TripletIdx tripletIdx = LUT.Speakers[index];
959 std::memcpy(&outSpeakersIndices[iout], &LUT.Data[tripletIdx].Tri, sizeof(SpeakerIdx) * 3);
960
961 // Extract gains
962 std::array<float, 4> gains{ 0.0f, 0.0f, 0.0f, 0.0f };
963 ExtractGains(index, direction, std::span<float, 3>(gains.data(), 3));
964
965 // If one of the speakedrs is dummy, we need to silence it
966 const uint8 dummyIndex = LUT.Data[tripletIdx].DummyIndex;
967
968#if !JPL_NORMALIZE_GAINS_WITH_DUMMY
969 // If the direction falls directly onto a dummy,
970 // we just assign the gains to the other speakers
971 // to ensure consistent output
973 {
974 gains[0] = 1.0f;
975 gains[1] = 1.0f;
976 gains[2] = 1.0f;
977 }
978 // Now it's safe to silence the dummy
979 gains[dummyIndex] = 0.0f;
980
981 // If we just computed gains, we need to normalize
982 // here after silencing the dummy.
984
985 //JPL_ASSERT(!Math::HasNans(Vec3Type(gains[0], gains[1], gains[2])));
986#else
987 gains[dummyIndex] = 0.0f;
988#endif
989 // Copy valid speaker gains to the output
990 std::memcpy(&outSpeakerGains[iout], gains.data(), sizeof(float) * 3);
991
992 JPL_ASSERT(Algo::IsNormalizedL2(std::span<float>(&outSpeakerGains[iout], 3)));
993 }
994 }
995 }
996 }
997
998 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
999 template<CVec3 Vec3Type>
1001 {
1002 VBAPCell cell;
1003 GainsFor(direction, cell);
1004
1005 outGains[cell.Speakers[0]] = cell.Gains[0];
1006 outGains[cell.Speakers[1]] = cell.Gains[1];
1007 outGains[cell.Speakers[2]] = cell.Gains[2];
1008 }
1009
1010 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
1011 JPL_INLINE void LUTQuery<LUTType, LUTCodec, Vec3TypeImplicit>::ExtractGains(int index, std::span<float, 3> outGains) const requires (bLUTHasGains)
1012 {
1013 if constexpr (std::same_as<typename LUTType::GainType, float>)
1014 {
1015 std::memcpy(outGains.data(), LUT.Gains[index].data(), sizeof(float) * 3);
1016 }
1017 else
1018 {
1019 static_assert(std::same_as<typename LUTType::GainType, Gain24Bit>);
1020
1021 // Unpack the gains to convert to float
1022 const GainPack<Gain24Bit>& pack = LUT.Gains[index];
1023 outGains[0] = pack[0]; outGains[1] = pack[1]; outGains[2] = pack[2];
1024 }
1025 }
1026
1027 template<CLUT LUTType, class LUTCodec, CVec3 Vec3TypeImplicit>
1028 template<CVec3 Vec3Type>
1029 JPL_INLINE void LUTQuery<LUTType, LUTCodec, Vec3TypeImplicit>::ExtractGains(int index, const Vec3Type& direction, std::span<float, 3> outGains) const requires (!bLUTHasGains)
1030 {
1031 // Get the triplet index from the LUT
1032 const TripletIdx triplet = LUT.Speakers[index];
1033
1034 // Compute gains
1035 const Vec3Type gainsV = LUT.Data[triplet].TrisInvMat.Transform(direction);
1037 }
1038
1039} // namespace JPL::VBAP
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
#define JPL_ENSURE(inExpression,...)
Define ENSURE.
Definition ErrorReporting.h:90
Definition ChannelMap.h:150
Utility class to encapsulate dummy speaker handling while building a LUT.
Definition DummySpeakers.h:36
JPL_INLINE constexpr uint32 GetNumDummies() const noexcept
Definition DummySpeakers.h:75
Forward declarations.
Definition VBAPLUT3D.h:280
JPL_INLINE uint32 GetNumDummies() const noexcept
Definition VBAPLUT3D.h:286
JPL_INLINE bool BuildForAllDirections()
Build the entire LUT for all directions.
Definition VBAPLUT3D.h:692
JPL_INLINE uint32 GetNumRealChannels() const noexcept
Definition VBAPLUT3D.h:287
bool ComputeCellFor(const Vec3Type &direction, int lutIndex)
Definition VBAPLUT3D.h:521
static constexpr bool cLUTHasDynamicData
Definition VBAPLUT3D.h:282
SpeakerTripletIdx SanitizeSpeakerIndex(const SpeakerTripletIdx &triplet) const
Definition VBAPLUT3D.h:505
float FindShortestAperture() const
Definition VBAPLUT3D.h:489
JPL_INLINE const std::pmr::vector< SpeakerTripletIdx > & GetTris() const noexcept
Definition VBAPLUT3D.h:288
Definition VBAPLUT3D.h:68
LUTQuery< LUTType, LUTCodec, Vec3Type > QueryType
Definition VBAPLUT3D.h:73
LUTBuilder< GetSpeakerVectorFunction, LUTCodec, Vec3Type, LUTType > BuilderType
Definition VBAPLUT3D.h:72
std::remove_cvref_t< decltype(GetSpeakerVectorFunction(EChannel{}))> Vec3Type
Definition VBAPLUT3D.h:70
static JPL_INLINE BuilderType MakeBuilder(ChannelMap channelMap, LUTType &lut)
Make LUTBuilder object to build LUT for given 'channelMap' and 'LUTType'.
Definition VBAPLUT3D.h:77
static JPL_INLINE QueryType Query(const LUTType &LUT)
Make LUTQuery object to query 'LUT' for speaker gains.
Definition VBAPLUT3D.h:83
Interface to query LUT gains for a direction.
Definition VBAPLUT3D.h:233
static constexpr bool bLUTHasGains
Definition VBAPLUT3D.h:238
void GainsFor(const Vec3Type &direction, VBAPCell &outSpeakerGains) const
Definition VBAPLUT3D.h:820
JPL_INLINE LUTQuery(const LUTType &lut) noexcept
Definition VBAPLUT3D.h:248
const LUTType & LUT
Definition VBAPLUT3D.h:273
Definition VBAPLUT3D.h:106
VBAP LUT interfaces only accept LUT types defined below.
Definition VBAPLUT3D.h:53
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
JPL_INLINE constexpr bool IsNearlyZero(T value, T errorTolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:146
JPL_INLINE constexpr bool IsNearlyEqual(T a, T b, T tolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:152
bool TriangulateSpeakerLayout(std::span< const Vec3Type > speakerVectors, Vec3iContainerType &outIndices)
Definition VBAPEx.h:138
std::array< uint8, 3 > Vec3i
Definition VBAPEx.h:89
Forward declaration.
Definition DummySpeakers.h:31
uint8 SpeakerIdx
Index of a speaker/channel.
Definition VBAPLUT3D.h:123
void * DynamicDataTypeNone
Definition VBAPLUT3D.h:149
uint8 TripletIdx
Definition VBAPLUT3D.h:130
std::array< SpeakerIdx, 3 > SpeakerTripletIdx
Direct indices of the speakers, corersponding to channels in the output buffer.
Definition VBAPLUT3D.h:126
ELUTSize
Definition VBAPLUT3D.h:94
void * GainTypeNone
LUT can be made out of just the selected speaker triplets, without precomputed gains.
Definition VBAPLUT3D.h:120
std::array< T, 3 > GainPack
Alias for speaker triplet gains.
Definition VBAPLUT3D.h:117
JPL_INLINE void SetZ(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:41
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
JPL_INLINE auto GetZ(const Vec3Type &v) noexcept
Definition Vec3Traits.h:37
std::uint32_t uint32
Definition Core.h:311
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
EChannel
Definition ChannelMap.h:39
@ TOP_Channels
Definition ChannelMap.h:75
@ TopCenter
Definition ChannelMap.h:58
@ LFE
Definition ChannelMap.h:43
JPL_INLINE auto GetY(const Vec3Type &v) noexcept
Definition Vec3Traits.h:36
std::uint8_t uint8
Definition Core.h:309
JPL_INLINE void SetX(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:39
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition ChannelMap.h:268
Minimal 3x3 matrix interface.
Definition MinimalMat.h:83
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:255
Definition VBAPLUT3D.h:153
std::pmr::vector< DynamicDataType > Data
Definition VBAPLUT3D.h:155
DynamicDataType_ DynamicDataType
Definition VBAPLUT3D.h:154
Definition VBAPLUT3D.h:136
SpeakerTripletIdx Tri
Definition VBAPLUT3D.h:137
uint8 DummyIndex
Definition VBAPLUT3D.h:138
Definition VBAPLUT3D.h:143
Math::Mat3< Vec3Type > TrisInvMat
Definition VBAPLUT3D.h:146
std::array< SpeakerIndexType, N > Speakers
Definition VBAPLUT3D.h:180
Base to compose LUT specializations.
Definition VBAPLUT3D.h:165
std::array< SpeakerIndexType, N > Speakers
Definition VBAPLUT3D.h:170
std::array< GainPack< GainType >, N > Gains
Definition VBAPLUT3D.h:169
GainType_ GainType
Definition VBAPLUT3D.h:166
SpeakerIndexType_ SpeakerIndexType
Definition VBAPLUT3D.h:167
Data extracted from LUT for specific source direction.
Definition VBAPLUT3D.h:243
GainPack< float > Gains
Definition VBAPLUT3D.h:244
SpeakerTripletIdx Speakers
Represents gains of speaker/out-channel triplet.
Definition VBAPLUT3D.h:245
Definition VBAPLUT3D.h:112
Definition MinimalVec2.h:29
Definition SIMD.h:207
Minimal 4-wide 32-bit float vector implementation for SIMD.
Definition SIMD.h:60
JPL_INLINE void store(float *mem) const
Store values from simd to provided memory location.
Definition SIMD.h:573
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97