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