JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
VBAPanning3D.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"
23
25
28
36
37#include <cstring>
38#include <vector>
39#include <ranges>
40#include <utility>
41#include <limits>
42#include <span>
43
44namespace JPL
45{
47 namespace VBAP
48 {
49 template<class Traits = VBAPStandardTraits, auto cLUTType = VBAP::ELUTSize::KB_983>
50 class Panning3D;
51 } // namespace VBAP
52
53 //======================================================================
56 template<class Traits = VBAPStandardTraits, auto cLUTType = VBAP::ELUTSize::KB_983>
58
61
62 namespace VBAP
63 {
64 //==================================================================
67 template<class Traits, auto cLUTType>
69 {
70 static_assert(VBAP::CLUTType<decltype(cLUTType)> && "cLUTType must be enum class VBAP::ELUTSize");
71
73 public:
74 using Vec3Type = typename Base::Vec3Type;
75
76 //======================================================================
79
83
86 &Traits::GetChannelVector,
89 >;
90
91 //======================================================================
94 {
96
97 // Note:
98 // Initializing SourceLayout dimensions with these defaults results in
99 // 'cMaxNumVirtualSources` directions to process when runtime data changes.
100 // See `Dimensions` comment
101 static constexpr size_t cMaxNumRings = 12; // Default max number of rings
102 static constexpr size_t cMaxNumSamples = 24; // Default max number of samples per ring
103 static constexpr size_t cMaxNumVirtualSources = cMaxNumRings * cMaxNumSamples; // Max number samples that can be generated for given VBAPdData
104
110 {
113
116
118 [[nodiscard]] JPL_INLINE size_t GetSize() const noexcept { return static_cast<size_t>(NumRings) * NumSamplesPerRing; }
119
121 [[nodiscard]] static Dimensions For(uint32 numChannels, float maxGeoDistanceDot = std::numeric_limits<float>::max());
122 };
123
124 public:
125 [[nodiscard]] JPL_INLINE Dimensions GetDimensions() const noexcept { return mDimensions; }
126 [[nodiscard]] JPL_INLINE size_t GetNumVirtualSources() const noexcept;
127 [[nodiscard]] JPL_INLINE static constexpr size_t GetMaxNumVirtualSources() noexcept;
128
132 [[nodiscard]] JPL_INLINE float GetMinDistanceBetweenSamples() const noexcept { return std::min(mPhiTerm, JPL_TWO_PI / mDimensions.NumSamplesPerRing); }
133
134 private:
135 friend Base;
138 bool Initialize(ChannelMap channelMap, ChannelMap targetMap, float shortestEdgeApertureDot = std::numeric_limits<float>::max());
139
140 void PrecomputeAzimuth();
141
145 void GenerateSpreadCap(Vec3SIMDBufferView& outBuffer, float spreadNormalized) const;
146#if 0 // scalar version for a reference
147 void GenerateSpreadCap(std::span<Vec3Type> outBuffer, float spreadNormalized) const;
148#endif
149 private:
150 Dimensions mDimensions; // Dimensions for virtual source distribution for a spread cap
151
152 // Phi is essentially distance between rings of samples.
153 // While Theta is the distance between two samples on a ring (2 * PI / num samples)
154 float mPhiTerm; // Cached term to generate spread cap.
155
156 // Cached sin/cos to generate spread cap
157 std::pmr::vector<simd> mSamplesSin{ GetDefaultMemoryResource() };
158 std::pmr::vector<simd> mSamplesCos{ GetDefaultMemoryResource() };
159 };
160
161 //======================================================================
164 [[nodiscard]] static inline std::optional<const char*> IsValidTargetChannelMap(ChannelMap channelMap)
165 {
166 if (!JPL_ENSURE(channelMap.HasTopChannels()))
167 return "Failed to initialize Panning3D, provided invalid target channel map. Panning3D requires channel map with top channels.";
168
169 if (channelMap.GetNumChannels() < 3)
170 return "Trying to initialize with < 3 channels, 3D panning is not possible for such layout.";
171
172 return std::nullopt;
173 }
174 };
175 } // namespace VBAP
176} // namespace JPL
177
178//==============================================================================
179//
180// Code beyond this point is implementation detail...
181//
182//==============================================================================
183
184// It may or may not be desireable to add channel center
185// direcection virtual source to the channel cap
186// (in the center of the inner most ring)
187#define JPL_ADD_CENTER_VS 0
188
189namespace JPL::VBAP
190{
191 //==========================================================================
192 namespace Internal
193 {
194 // Compute dot between neighbors for `numPoints` on a cap rim of `angularDiameter` of a cap.
195 [[nodiscard]] static JPL_INLINE float ComputeNeigbourSampleDotFromN(float angularDiameter, uint32 numPoints)
196 {
197 if (numPoints <= 1)
198 return 1.0f; // only one point: zero separation
199 const float alpha = 0.5f * angularDiameter;
200 const auto [s, c] = Math::SinCos(alpha);
201 const float s2 = s * s;
202 const float c2 = c * c;
203 const float dphi = JPL_TWO_PI / static_cast<float>(numPoints);
204 const float dot = std::clamp(s2 * std::cos(dphi) + c2, -1.0f, 1.0f);
205 return dot;
206 }
207
208 // Geodesic angle between neighbors for N points.
209 // Compute geodesic angle between neighbors for `numPoints` and `angularDiameter` of a cap.
210 [[nodiscard]] static JPL_INLINE float ComputeNeighbourAngleFromN(float angularDiameter, uint32 numPoints)
211 {
212 return std::acos(ComputeNeigbourSampleDotFromN(angularDiameter, numPoints));
213 }
214
215
216 // Minimum number of points so that geodesic gap < m.
217 // Calculate minimum number of points for cap of `angularDiameter`
218 // so that geodesic gap between them is < m.
219 [[nodiscard]] static JPL_INLINE uint32 ComputeMinNumPointsOnCapRing(float angularDiameter, float dotM, uint32 maxNumSamplesFallback)
220 {
221 const float alpha = 0.5f * angularDiameter;
222 const auto [s, c] = Math::SinCos(alpha);
223 const float s2 = s * s;
224 const float c2 = c * c;
225
226 if (!JPL_ENSURE(s2 != 0.0))
227 return 1u; // degenerate cap: a point
228
229 // target cos
230 const float rhs = std::clamp((dotM - c2) / s2, -1.0f, 1.0f);
231 const float dphi_max = std::acos(rhs);
232
233 if (!JPL_ENSURE(dphi_max > 0.0f))
234 return maxNumSamplesFallback; // m too small to place neighboring samples
235
236 return static_cast<uint32>(std::ceil(JPL_TWO_PI / dphi_max));
237 };
238 }
239
240 //==========================================================================
241 template<class Traits, auto cLUTType>
242 inline bool Panning3D<Traits, cLUTType>::SourceLayout::Initialize(ChannelMap channelMap, ChannelMap targetMap, float shortestEdgeApertureDot)
243 {
244 if (auto error = IsValidSourceChannelMap(channelMap))
245 {
246 JPL_ERROR_TAG("VBAPanner2D", error.value());
247 return false;
248 }
249
250 // Sanitize input parameters, we don't use LFE for panning
251 const uint32 numChannels = channelMap.GetNumChannels() - channelMap.HasLFE();
252
253 // Create sample distribution for the number of channels
254 // and smallest geodesic distance between output channel vectors
255 mDimensions = Dimensions::For(numChannels, shortestEdgeApertureDot);
256
257 JPL_ASSERT(mDimensions.GetSize() <= cMaxNumVirtualSources);
258
259 // Precompute phi term to generte rings
260 mPhiTerm = numChannels == 1
261 ? JPL_PI / (mDimensions.NumRings + 1)
262 : JPL_PI / (mDimensions.NumRings * numChannels + numChannels * 0.5f);
263 // +1 for mono avoids last rim at spread 1.0 to fall onto a single point
264 // and to distribute positions more evenly, since each position
265 // is "centre" rather than edge of the contribution to the field
266 //
267 // + numChannels * 0.5 adds the gaps between channel caps into account,
268 // to make them equal to the gap between rings
269
270 // Precompute azimuth of each ring
271 PrecomputeAzimuth();
272
273 const auto numVirtualSorucesPerChannel = static_cast<uint32>(mDimensions.GetSize());
274 return LayoutBase::InitializeBase(channelMap, targetMap, numVirtualSorucesPerChannel);
275 }
276
277 template<class Traits, auto cLUTType>
278 inline void Panning3D<Traits, cLUTType>::SourceLayout::PrecomputeAzimuth()
279 {
280 // TODO: if halfSamples is stil quite large, we might want to still resort to quarter + double mirror
281
282 const uint32 numSIMDOps = GetNumSIMDOps(mDimensions.NumSamplesPerRing);
283 JPL_ASSERT(numSIMDOps >= 1);
284
285 // TODO: this is valid for single axis mirroring, for 2 axis we need half samples to also be divisible,
286 const std::size_t toMirror = FloorToDiv2(numSIMDOps);
287 const std::size_t tail = GetDiv2Tail(numSIMDOps);
288
289 // How much we can potentially "mirror"
290 const uint32 halfSamples = toMirror >> 1;
291
292 // Generate half that we can mirror + tail,
293 // mirrored only the half
294 const uint32 halfAndTail = halfSamples + tail;
295
296 mSamplesSin.resize(halfAndTail);
297 mSamplesCos.resize(halfAndTail);
298
299 const float thetaTerm = JPL_TWO_PI / mDimensions.NumSamplesPerRing;
300 static const simd rampFirst(0.5f, 1.5f, 2.5f, 3.5f);
301
302 simd theta = simd(thetaTerm) * rampFirst;
303 const simd thetaDelta(thetaTerm * 4.0f);
304
305 for (uint32 i = 0; i < halfAndTail; ++i)
306 {
307 Math::SinCos(theta, mSamplesSin[i], mSamplesCos[i]);
308 theta += thetaDelta;
309 }
310 }
311
312#if 0
313 template<class Traits, auto cLUTType>
314 inline void Panning3D<Traits, cLUTType>::SourceLayout::GenerateSpreadCap(std::span<Vec3Type> outBuffer, float spreadNormalized) const
315 {
316 /*
317 TOTAL COST (8/32 ring/samples):
318 - 8 sin/cos
319 - 130 mul
320 - 10 add
321 - 2 bit shifts
322 - 192 flipping sing of a float (x = -x)
323
324 At 4.5 GHz roughly 1274 cycles, ~0.28 us or 0.00028 ms
325 */
326
327 JPL_ASSERT(outBuffer.size() >= mDimensions.GetSize() + JPL_ADD_CENTER_VS);
328
329 // Small static buffer should be enough
330 static constexpr size_t BUF_SIZE = SourceLayout::cMaxNumSamples;
331 std::pair<float, float> ringSCsBuffer[BUF_SIZE];
332
333 JPL_ASSERT(Math::IsPositiveAndBelow(mDimensions.NumRings, BUF_SIZE + 1));
334
335 auto ringCSs = ringSCsBuffer | std::views::take(mDimensions.NumRings);
336
337 // Precompute centre of each ring
338 {
339 const float capSpread = (1.0f - spreadNormalized);
340 const float phiTerm = capSpread * mPhiTerm;
341
342 float phi = phiTerm;
343 for (auto& ringCS : ringCSs)
344 {
345 ringCS = Math::SinCos(phi);
346 phi += phiTerm;
347 }
348 }
349
350 Vec3Type* destination = outBuffer.data();
351
352 // Generate canon sphere quadrant
353 for (const auto& [sinPhi, cosPhi] : ringCSs)
354 {
355 for (uint32 i = 0; i < mSamplesSin.size(); ++i)
356 {
357 const float sinTheta = mSamplesSin[i];
358 const float cosTheta = mSamplesCos[i];
359 *destination++ = Vec3Type{
360 sinPhi * cosTheta,
361 sinPhi * sinTheta,
362 cosPhi
363 };
364 }
365 }
366
367 // Generate the rest of sphere by mirroring vectors
368 {
369 const uint32 quadrantSamples = mDimensions.NumSamplesPerRing >> 2;
370 const uint32 quadrantSize = mDimensions.NumRings * quadrantSamples;
371 const uint32 halfSphereSize = quadrantSize << 1;
372
373 // Duplicate first quadrant
374 std::copy(outBuffer.begin(), outBuffer.begin() + quadrantSize, destination);
375
376 // Mirror duplicated first quadrant to make a half sphere
377 for (Vec3Type& mirror : std::span(destination, quadrantSize))
378 mirror.X = -mirror.X;
379
380 // Advance destination buffer pointer
381 destination += quadrantSize;
382
383 // Duplicate half sphere
384 std::copy(outBuffer.begin(), outBuffer.begin() + halfSphereSize, destination);
385
386 // Mirror half sphere to make a whole sphere
387 for (Vec3Type& mirror : std::span(destination, halfSphereSize))
388 mirror.Y = -mirror.Y;
389 }
390
391 /* TODO: BETTER DISTRIBUTION.We could use a better distribution of samples, to keep the density more or less equal, or just different.
392 - right now 8 samples at the 1st ring is much more dense than 8 samples at the ring around poles
393 - this may or may not be desireable for the resulting gains
394 */
395
396#if JPL_ADD_CENTER_VS
397 // Add center point
398 outBuffer[i] = Vec3(0.0f, 0.0f, 1.0f);
399#endif
400 }
401#endif
402
403 template<class Traits, auto cLUTType>
404 inline void Panning3D<Traits, cLUTType>::SourceLayout::GenerateSpreadCap(Vec3SIMDBufferView& outBuffer, float spreadNormalized) const
405 {
406 // Small static buffer should be enough
407 static constexpr size_t BUF_SIZE = cMaxNumSamples;
408 JPL_ASSERT(Math::IsPositiveAndBelow(mDimensions.NumRings, BUF_SIZE + 1));
409
410 StaticArray<float, BUF_SIZE> ringSs(mDimensions.NumRings);
411 StaticArray<float, BUF_SIZE> ringCs(mDimensions.NumRings);
412
413 // Precompute centre of each ring
414 {
415 static const simd ramp(1.0f, 2.0f, 3.0f, 4.0f);
416
417 const float capSpread = (1.0f - spreadNormalized);
418 const float phiTermS = capSpread * mPhiTerm;
419
420 simd phiTerm = simd(phiTermS) * ramp;
421 const simd delta(4.0f * phiTermS);
422
423 uint32 i = 0;
424 for (; i < FloorToSIMDSize(ringCs.size()); i += simd::size())
425 {
426 simd ringSin, ringCos;
427 Math::SinCos(phiTerm, ringSin, ringCos);
428 ringSin.store(&ringSs[i]);
429 ringCos.store(&ringCs[i]);
430
431 phiTerm += delta;
432 }
433
434 const uint32 tail = GetSIMDTail(ringCs.size());
435 float phiTermTail[4]{};
436 phiTerm.store(phiTermTail);
437
438 for (uint32 t = 0; t < tail; ++t, ++i)
439 {
440 const auto [ringSin, ringCos] = Math::SinCos(phiTermTail[t]);
441 ringSs[i] = ringSin;
442 ringCs[i] = ringCos;
443 }
444 }
445
446 simd* destX = outBuffer.X;
447 simd* destY = outBuffer.Y;
448 simd* destZ = outBuffer.Z;
449
450 const uint32 numSIMDOps = GetNumSIMDOps(mDimensions.NumSamplesPerRing);
451 JPL_ASSERT(numSIMDOps >= 1);
452 JPL_ASSERT(outBuffer.size() == numSIMDOps * mDimensions.NumRings + JPL_ADD_CENTER_VS);
453
454 // TODO: this is valid for single axis mirroring, for 2 axis we need half samples to also be divisible by 2
455 const std::size_t toMirror = FloorToDiv2(numSIMDOps);
456 const std::size_t tail = GetDiv2Tail(numSIMDOps);
457
458 // How much we can potentially "mirror"
459 const uint32 halfSamples = toMirror >> 1;
460
461 // Generate half that we can mirror + tail,
462 // mirrored only the half
463 const uint32 halfAndTail = halfSamples + tail;
464
465 // Generate canon sphere half (or fuill, if NumSamplesPerRing == simd::size())
466 if (halfSamples)
467 {
468 simd* ringsX = destX;
469 simd* ringsY = destY;
470 simd* ringsZ = destZ;
471
472 for (uint32 sci = 0; sci < ringSs.size(); ++sci)
473 {
474 const auto sinPhi = simd(ringSs[sci]);
475 const auto cosPhi = simd(ringCs[sci]);
476
477 for (uint32 i = 0; i < halfSamples; ++i)
478 {
479 ringsX[i] = sinPhi * mSamplesCos[i];
480 ringsY[i] = sinPhi * mSamplesSin[i];
481 ringsZ[i] = cosPhi;
482 }
483
484 ringsX += halfSamples;
485 ringsY += halfSamples;
486 ringsZ += halfSamples;
487 }
488
489 const uint32 totalNumHalfSamples = ringSs.size() * halfSamples;
490 const std::size_t sizeOfHalfSamples = totalNumHalfSamples * sizeof(simd);
491
492 // Duplicate half sphere
493 std::memcpy(ringsX, destX, sizeOfHalfSamples);
494 std::memcpy(ringsZ, destZ, sizeOfHalfSamples);
495
496 // Mirror half sphere to make a whole sphere
497 for (uint32 i = 0; i < totalNumHalfSamples; ++i)
498 {
499 ringsY[i] = -destY[i];
500 }
501 destX = &ringsX[totalNumHalfSamples];
502 destY = &ringsY[totalNumHalfSamples];
503 destZ = &ringsZ[totalNumHalfSamples];
504 }
505
506 if (tail)
507 {
508 for (uint32 sci = 0; sci < ringSs.size(); ++sci)
509 {
510 const auto sinPhi = simd(ringSs[sci]);
511 const auto cosPhi = simd(ringCs[sci]);
512
513 destX[sci] = sinPhi * mSamplesCos[halfSamples];
514 destY[sci] = sinPhi * mSamplesSin[halfSamples];
515 destZ[sci] = cosPhi;
516 }
517 }
518 }
519
520 template<class Traits, auto cLUTType>
522 {
523 return mDimensions.GetSize() + JPL_ADD_CENTER_VS;
524 }
525
526 template<class Traits, auto cLUTType>
528 {
529 return cMaxNumVirtualSources + JPL_ADD_CENTER_VS;
530 }
531
532 //==========================================================================
533 template<class Traits, auto cLUTType>
535 {
536 // Note: Initializing SourceLayout dimensions with default constant dimensions (8, 32)
537 // results in 256 directions to process when runtime data changes, which can be a lot.
538 //
539 // So we look for the minimal viable number of rings and samples per ring,
540 // while avoiding situation where we have no sources between any 2 speakers.
541 // So we find the smallest angular distance between too consecutive speakers
542 // and make sure the largest possible geodesic distance between 2 samples is smaller.
543 //
544 // (this would not be needed for user-defined VS maps, only for the default ones that use spread/focus)
545
546 uint32 totalRings = static_cast<uint32>(cMaxNumRings);
547 uint32 samplesPerRing = static_cast<uint32>(cMaxNumSamples);
548
549 if (maxGeoDistanceDot < std::numeric_limits<float>::max())
550 {
551 JPL_ASSERT(maxGeoDistanceDot >= -1.000001f && maxGeoDistanceDot <= 1.000001f);
552
553 // 1. Estimate number of rings required to avoid inactive speaker edges
554
555 // Since we distribute our channels along the equator,
556 // we can just divide the circumference by max allowed aperture
557 const float m = std::acos(maxGeoDistanceDot); // geodesic threshold (radians)
558 //totalRings = static_cast<uint32>(std::ceil(JPL_TWO_PI / std::max(m, 1e-6f)));
560 totalRings = static_cast<uint32>(std::ceil(JPL_PI / std::max(m, 1e-6f)));
561
562 // circumference of a cap rim given angular diameter d
563 // static auto getCapCircumference = [](float d) { return JPL_TWO_PI * std::sin(0.5f * d); };
564
565 // 2. Estimate number of samples per ring required to avoid inactive speaker edges
566
567 if (numChannels <= 2)
568 {
569 // For 1-2 channels we can reuse the same distribution as totalRings
570 // since each channel's cap can reach a circumference between poles
571 // at spread 1.0, focus 0.0.
572 // (x2 because unlike ring slices, samples spread the entire circumfrance)
573 samplesPerRing = totalRings << 1;
574 }
575 else
576 {
577 // ..while for > 2 channels we need to compute geodesic distance
578 // to estimate minimum number of samples per ring
579
580 const float angularDiameter = JPL_TWO_PI / numChannels;
581 samplesPerRing = Internal::ComputeMinNumPointsOnCapRing(angularDiameter, maxGeoDistanceDot, cMaxNumSamples);
582
583#if defined(JPL_DEBUG) || defined(JPL_TEST)
584 {
585 const float dotActual = Internal::ComputeNeigbourSampleDotFromN(angularDiameter, samplesPerRing);
586 // because of ceil quantization, actual neighbor dot should always be >= requested
587 JPL_ASSERT(dotActual + 1e-6f >= maxGeoDistanceDot);
588 }
589#endif // JPL_DEBUG || JPL_TEST
590 }
591 }
592
593 // For high channel count target (i.e. small min speaker aperture)
594 // we can overflow max virtual sources, so we need to clamp it
595 if (RoundUpBy4(samplesPerRing) * totalRings > cMaxNumVirtualSources)
596 {
597 // Floor to divisible by simd size
598 samplesPerRing = FloorToSIMDSize(cMaxNumVirtualSources / totalRings);
599 }
600
601 // Create sample distribution for the number of channels.
602 return Dimensions{
603 // Sacrificin vectorization for rings seems worth it,
604 // since it reduces the number of samples we need to comput by the order of magnitude.
605 .NumRings = std::max(1u,
606 static_cast<uint32>(std::ceil(totalRings / static_cast<float>(numChannels)))),
607 // we need to ceil because we can loose almost half the rings required to fill the gaps
608 // for high channel count source (e.g. 12 rings / 7 channels, we get 1, and loose 5 rings total)
609
610 .NumSamplesPerRing = RoundUpBy4(samplesPerRing)
611 };
612 }
613
614} // 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
#define JPL_ERROR_TAG(tag, message)
Definition ErrorReporting.h:128
#define JPL_ADD_CENTER_VS
Definition VBAPanning3D.h:187
Definition ChannelMap.h:154
constexpr uint32 GetNumChannels() const noexcept
Definition ChannelMap.h:166
constexpr bool HasTopChannels() const noexcept
Definition ChannelMap.h:163
constexpr bool HasLFE() const noexcept
Definition ChannelMap.h:162
Forward declaration.
Definition DirectionEncoding.h:111
static constexpr size_t cAxisRange
Definition DirectionEncoding.h:122
Definition VBAPLUT3D.h:68
Definition VBAPanning3D.h:69
static std::optional< const char * > IsValidTargetChannelMap(ChannelMap channelMap)
Definition VBAPanning3D.h:164
typename Base::Vec3Type Vec3Type
Definition VBAPanning3D.h:74
static constexpr size_t cLUTSize
Definition VBAPanning3D.h:78
Octahedron16Bit LUTCodec
Definition VBAPanning3D.h:77
Definition PannerBase.h:227
typename Traits::Vec3Type Vec3Type
Aliases to avoid typing wordy templates.
Definition PannerBase.h:230
Definition VBAPLUT3D.h:106
JPL_INLINE constexpr bool IsPositiveAndBelow(T1 value, T2 below) noexcept
Definition Math.h:140
JPL_INLINE std::pair< T, T > SinCos(T value) noexcept
Definition Math.h:164
Forward declaration.
Definition DummySpeakers.h:31
Definition AcousticMaterial.h:36
JPL_INLINE constexpr auto GetNumSIMDOps(std::unsigned_integral auto count) noexcept
Get number of SIMD operations that can fit into the count
Definition SIMDMath.h:51
std::uint32_t uint32
Definition Core.h:311
JPL_INLINE constexpr auto FloorToSIMDSize(std::unsigned_integral auto count) noexcept
Floor count to 4-wide simd vector.
Definition SIMDMath.h:33
JPL_INLINE constexpr auto GetSIMDTail(std::unsigned_integral auto count) noexcept
Get the remaining tail from count that won't fill a simd vector.
Definition SIMDMath.h:45
Vec3BufferView< simd > Vec3SIMDBufferView
View into a Vec3-like SoA buffer, holding separate arrays of Vec3 components X, Y,...
Definition Vec3Buffer.h:56
JPL_INLINE std::optional< const char * > IsValidSourceChannelMap(ChannelMap channelMap)
Definition PannerBase.h:193
constexpr T RoundUpBy4(T n) noexcept
Definition Bits.h:52
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
JPL_INLINE constexpr auto GetDiv2Tail(std::unsigned_integral auto count) noexcept
Definition SIMDMath.h:81
JPL_INLINE constexpr auto FloorToDiv2(std::unsigned_integral auto count) noexcept
Floor count to divisible by 2.
Definition SIMDMath.h:75
OctahedronEncoding< Octahedron::Precision8bits > Octahedron16Bit
Definition DirectionEncoding.h:103
Definition VBAPLUT3D.h:112
uint32 NumSamplesPerRing
Number samples (virtual soruces) to generate for each ring of a spread cap.
Definition VBAPanning3D.h:115
uint32 NumRings
Number of rings to generate for a spread cap.
Definition VBAPanning3D.h:112
static Dimensions For(uint32 numChannels, float maxGeoDistanceDot=std::numeric_limits< float >::max())
Calculate dimensions for numChannels with maxGeoDistanceDot between points.
Definition VBAPanning3D.h:534
JPL_INLINE size_t GetSize() const noexcept
Get total number of virtual sources for these dimentions.
Definition VBAPanning3D.h:118
Implementation of the source layout for 2D panning.
Definition VBAPanning3D.h:94
static JPL_INLINE constexpr size_t GetMaxNumVirtualSources() noexcept
Definition VBAPanning3D.h:527
static constexpr size_t cMaxNumRings
Definition VBAPanning3D.h:101
JPL_INLINE float GetMinDistanceBetweenSamples() const noexcept
Definition VBAPanning3D.h:132
JPL_INLINE Dimensions GetDimensions() const noexcept
Definition VBAPanning3D.h:125
static constexpr size_t cMaxNumSamples
Definition VBAPanning3D.h:102
static constexpr size_t cMaxNumVirtualSources
Definition VBAPanning3D.h:103
typename Base::VBAPLayoutBase LayoutBase
Definition VBAPanning3D.h:95
JPL_INLINE size_t GetNumVirtualSources() const noexcept
Definition VBAPanning3D.h:521
Definition PannerBase.h:281
Definition Vec3Buffer.h:89
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97