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