JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
VBAPanning2D.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"
24
28
34
35#include <algorithm>
36#include <cmath>
37#include <cstring>
38#include <limits>
39#include <optional>
40#include <span>
41
42namespace JPL
43{
45 namespace VBAP
46 {
47 template<class Traits = VBAPStandartTraits>
48 class Panning2D;
49 } // namespace VBAP
50
51
52 //======================================================================
55 template<class Traits = VBAPStandartTraits>
57
58 namespace VBAP
59 {
60 //==================================================================
63 template<class Traits>
65 {
67 public:
68 using Vec3Type = typename Base::Vec3Type;
69
70 //==================================================================
72
75 &Traits::GetChannelVector,
76 &Traits::GetChannelAngle
77 >;
78
79 //=================================================================
82 {
84
85 static constexpr size_t cMaxNumVirtualSources = 32;
86 public:
87
88 [[nodiscard]] JPL_INLINE size_t GetNumVirtualSources() const noexcept { return mNumVirtualSourcesPerChannel; }
90
95
96 private:
97 friend Base;
100 bool Initialize(ChannelMap channelMap, ChannelMap targetMap, float shortestEdgeApertureDot = std::numeric_limits<float>::max());
101
105 void GenerateSpreadCap(Vec3SIMDBufferView& outBuffer, float spreadNormalized) const;
106#if 0 // scalar version for a reference
107 void GenerateSpreadCap(std::span<Vec3Type> outBuffer, float spreadNormalized) const;
108#endif
109
110 private:
111 uint32 mNumVirtualSourcesPerChannel;
112
113 // Here phi is the distance between two samples on a ring (2 * PI / num samples)
114 float mPhiTerm; // Cached term to generate spread cap
115 float mOffsetTerm;
116 };
117
118 //==================================================================
121 [[nodiscard]] static inline std::optional<const char*> IsValidTargetChannelMap(ChannelMap channelMap)
122 {
123 if (!channelMap.IsValid())
124 return "Channel map is invalid.";
125
126 // TODO: do we want to use the same class for both, 2D and 3D cases and just swap internal handling?
127 if (!JPL_ENSURE(!channelMap.HasTopChannels()))
128 return "Channel map has top channels, cannob be handles by 2D panner.";
129
130 if (channelMap.GetNumChannels() == 1)
131 return "Trying to initialize with single target channel, panning is not possible.";
132
133 return std::nullopt;
134 }
135 };
136 } // namespace VBAP
137} // namespace JPL
138
139//==============================================================================
140//
141// Code beyond this point is implementation detail...
142//
143//==============================================================================
144namespace JPL::VBAP
145{
146 template<class Traits>
147 inline bool Panning2D<Traits>::SourceLayout::Initialize(ChannelMap channelMap, ChannelMap targetMap, float shortestEdgeApertureAngle)
148 {
150 {
151 JPL_ERROR_TAG("Panning2D", error.value());
152 return false;
153 }
154
155 // Sanitize input parameters, we don't use LFE for panning and VS per channel should be at least 2
156 const uint32 numChannels = channelMap.GetNumChannels() - channelMap.HasLFE();
157
158 mNumVirtualSourcesPerChannel = [](uint32 numChannels, float shortestEdgeApertureAngle)
159 {
160 // 'shortestEdgeApertureAngle' is geodesic threshold in radians
161 uint32 totalRings = 4;
162 if (shortestEdgeApertureAngle < std::numeric_limits<float>::max())
163 {
165
166 // Estimate number of virtual source required to avoid inactive speakers
167
168 // Since we distribute our channels along the equator,
169 // we can just divide the circumference by max allowed aperture
170 totalRings = static_cast<uint32>(std::ceil(JPL_TWO_PI / std::max(shortestEdgeApertureAngle, 1e-6f)));
171 }
172
173 // TODO: totalRings can be smaller than numChannels,
174 // if we're willing to sacrifice vectorization,
175 // we can move max out:
176 // std::max(RoundUpBy4(totalRings / numChannels), 2u))
177 return RoundUpBy4(std::max(totalRings / numChannels, 1u));
179
180 JPL_ASSERT(mNumVirtualSourcesPerChannel * numChannels <= cMaxNumVirtualSources);
181
182 // Cache for arranging virtual sources later
183 mPhiTerm = JPL_TWO_PI / (mNumVirtualSourcesPerChannel * numChannels);
184
185 // As we increase the focus, we need to offset the start of the cap to contract towards channel center angle.
186 // Note: this doesn't apply for mono source, since we mirror half samples around the channel center angle.
187 mOffsetTerm = numChannels > 1 ? mPhiTerm * mNumVirtualSourcesPerChannel * 0.5f : 0.0f;
188
189 return LayoutBase::InitializeBase(channelMap, targetMap, mNumVirtualSourcesPerChannel);
190 }
191
192 template<class Traits>
193 inline void Panning2D<Traits>::SourceLayout::GenerateSpreadCap(Vec3SIMDBufferView& outBuffer, float spreadNormalized) const
194 {
195 JPL_ASSERT(outBuffer.size() == GetNumSIMDOps(mNumVirtualSourcesPerChannel));
196
197 simd* destX = outBuffer.X;
198 simd* destY = outBuffer.Y;
199 simd* destZ = outBuffer.Z;
200
201 // We don't use Y coordinate in 2D panning
202 std::fill(destY, destY + outBuffer.size(), simd::zero());
203
204 const uint32 toMirror = static_cast<uint32>(FloorToDiv2(outBuffer.size()));
205 const uint32 tail = static_cast<uint32>(GetDiv2Tail(outBuffer.size()));
206
207 // How much we can potentially "mirror"
208 const uint32 halfSamples = toMirror >> 1;
209
210 // Generate half that we can mirror + tail,
211 // mirror only that half
213
214 // Generate canon half ring
215 {
216 const float spread = (1.0f - spreadNormalized);
217 const float phiTerm = spread * mPhiTerm; // apply spread
218 const simd offset(-spread * mOffsetTerm);
219 const simd rampFirst = simd(0.5f, 1.5f, 2.5f, 3.5f); // start with an offset to make mirroring work
220
222 const simd delta(4.0f * phiTerm);
223
224 for (uint32 i = 0; i < halfAndTail; ++i)
225 {
226 Math::SinCos(phi, (*destX++), (*destZ++));
227 phi += delta;
228 }
229 }
230
231 // Generate the rest of ring by mirroring vectors
232 if (halfSamples)
233 {
234 // Copy first half ring's only Z component
235 std::memcpy(destZ, outBuffer.Z, halfSamples * sizeof(simd));
236
237 // Copy and flip first half ring's X component to make a full ring
238 for (uint32 i = 0; i < halfSamples; ++i)
239 {
240 (*destX++) = -outBuffer.X[i];
241 }
242 }
243 }
244
245#if 0
246 template<class Traits>
247 inline void Panning2D<Traits>::SourceLayout::GenerateSpreadCap(std::span<Vec3Type> outBuffer, float spreadNormalized) const
248 {
249 /*
250 TOTAL COST (for 16 samples):
251 - 8 sin/cos
252 - 2 mul
253 - 11 add
254 - 1 bit shift
255 - 8 flipping sing of a float (x = -x)
256
257 At 4.5 GHz roughly 452 cycles, ~0.1 us or 0.0001004 ms
258 */
259
260 JPL_ASSERT(outBuffer.size() >= mNumVirtualSourcesPerChannel);
261
262 const uint32 halfSamples = mNumVirtualSourcesPerChannel >> 1;
264
265 // Generate canon half ring
266 {
267 const float phiTerm = (1.0f - spreadNormalized) * mPhiTerm; // apply spread
268 float phi = phiTerm * 0.5f; // start with an offset to make mirroring work
269
270 for (uint32 i = 0; i < halfSamples; ++i)
271 {
272 const auto& [sinPhi, cosPhi] = Math::SinCos(phi);
273
275 sinPhi,
276 0.0f,
277 cosPhi
278 };
279
280 phi += phiTerm;
281 }
282
283 // Advance destination buffer pointer
285 }
286
287 // Generate the rest of ring by mirroring vectors
288 {
289 // Duplicate first half ring
290 std::copy(outBuffer.begin(), outBuffer.begin() + halfSamples, destination);
291
292 // Mirror duplicated first half ring to make a full ring
295 }
296 }
297#endif
298} // 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
Definition ChannelMap.h:150
Look-up table containing channel gains for each direction.
Definition VBAPLUT2D.h:56
Definition VBAPLUT2D.h:227
Definition VBAPanning2D.h:65
typename Base::Vec3Type Vec3Type
Definition VBAPanning2D.h:68
static std::optional< const char * > IsValidTargetChannelMap(ChannelMap channelMap)
Definition VBAPanning2D.h:121
Definition PannerBase.h:227
typename Traits::Vec3Type Vec3Type
Aliases to avoid typing wordy templates.
Definition PannerBase.h:230
JPL_INLINE constexpr T FMA(T a, T b, T c) noexcept
Inlined fuse multiply-add. Compiler in some circumstances is more eager to optimize this than std::fm...
Definition Math.h:186
JPL_INLINE 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 auto GetX(const Vec3Type &v) noexcept
Definition Vec3Traits.h:35
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
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 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
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
Implementation of the source layout for 2D panning.
Definition VBAPanning2D.h:82
static constexpr size_t cMaxNumVirtualSources
Definition VBAPanning2D.h:85
JPL_INLINE float GetMinDistanceBetweenSamples() const noexcept
Definition VBAPanning2D.h:94
JPL_INLINE size_t GetNumVirtualSources() const noexcept
Definition VBAPanning2D.h:88
static JPL_INLINE constexpr size_t GetMaxNumVirtualSources() noexcept
Definition VBAPanning2D.h:89
typename Base::VBAPLayoutBase LayoutBase
Definition VBAPanning2D.h:83
Definition PannerBase.h:281
Definition Vec3Buffer.h:89
static JPL_INLINE simd zero() noexcept
Vector with all zeros.
Definition SIMD.h:541
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97