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