JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
VBAPEx.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"
25
30
33
34#include <vector>
35#include <array>
36#include <span>
37#include <functional>
38#include <compare>
39#include <algorithm>
40#include <memory>
41
42namespace JPL
43{
47 template<CVec3 Vec3Type>
48 [[nodiscard]] inline Vec3Type ComputeVBAP(const Vec3Type& sourceDirection, const Vec3Type& triPointA, const Vec3Type& triPointB, const Vec3Type& triPointC)
49 {
52 if (!JPL_ENSURE(L.TryInverse(Linv)))
53 {
54 // Loudspeakers are coplanar, cannot pan in 3D.
55
56 // (this is the case for any speaker setup where spreakers are at Y=0 plane
57 // and the source is also at Y=0, and instead of the vertical triangle,
58 // the bottom plane triangle was selected)
59 //
60 // In such case one can fall back to 2D or 1D panning
61 return Vec3Type{ 0.0f, 0.0f, 0.0f };
62 }
63
64 return Linv.Transform(sourceDirection);
65 }
66
74 {
77 if (!JPL_ENSURE(L.TryInverse(Linv)))
78 {
79 // 1D panning should handle this case.
80 // Loudspeaker vectors are collinear.
81 return Vec2{ 0.0f, 0.0f };
82 }
83
84 return Linv.Transform(sourceDirection);
85 }
86
87 namespace SpeakerTriangulation
88 {
89 using Vec3i = std::array<uint8, 3>;
90
91 template<auto GetSpeakerVectorFunction, class Vec3ContainerType>
93 {
94 if (!channelMap.IsValid())
95 return false;
96
97 const uint32 numChannels = channelMap.GetNumChannels() - channelMap.HasLFE();
98
99 outVectors.clear();
100 outVectors.reserve(numChannels);
101
102 // TODO: do we want to use indices of the channel map, or of the vector we store valid directions in?
103 channelMap.ForEachChannel([&outVectors](EChannel channel)
104 {
105 if (channel != EChannel::LFE)
107 });
108
109 return true;
110 }
111
112 template<auto GetSpeakerVectorFunction, class Vec3ContainerType, class IndexContainerType>
114 {
115 if (!channelMap.IsValid())
116 return false;
117
118 const uint32 numChannels = channelMap.GetNumChannels() - channelMap.HasLFE();
119
120 outVectors.clear();
121 outMapIndices.clear();
122 outVectors.reserve(numChannels);
123 outMapIndices.reserve(numChannels);
124
125 channelMap.ForEachChannel([&outVectors, &outMapIndices](EChannel channel, uint32 index)
126 {
127 if (channel != EChannel::LFE)
128 {
130 outMapIndices.push_back(index);
131 }
132 });
133
134 return true;
135 }
136
137 template<CVec3 Vec3Type, class Vec3iContainerType>
139 {
140 const auto& vertices = speakerVectors;
141
142 // For 3 speakers we only have one triangle, no need to build a hull.
143 if (vertices.size() == 3)
144 {
145 outIndices.push_back({ 0, 1, 2 });
146 return true;
147 }
148
149 /* TODO: we might want to rethink triangulation and instead of forcing creating closed convex hull
150 create only viable triangulated sections.
151 As per Pulkki:
152 If the specified virtual source direction is outside of the panning
153 directions possible with the current loudspeaker setup,
154 vbap object finds the nearmost triangle and it applies the sound to it.
155 */
156
158
160
161 const char* errorMessage = nullptr;
162
163 if (builder.Initialize(INT_MAX, 0.0f, errorMessage) != HullBuilderType::EResult::Success)
164 return false;
165
166 if (!JPL_ENSURE(builder.GetNumVerticesUsed() == vertices.size()))
167 return false;
168
169 builder.GetTriangles(outIndices);
170
171 return true;
172 }
173
174 template<auto GetSpeakerVectorFunction, CVec3 Vec3Type, class Vec3ContainerType, class Vec3iContainerType>
176 {
177 if (!channelMap.IsValid())
178 return false;
179
180 // 2D speaker arrangement
181 if (!channelMap.HasTopChannels())
182 return false;
183
184 const uint32 numChannels = channelMap.GetNumChannels() - channelMap.HasLFE();
185
186 // We need at least 3 indices to form a triangle
187 if (numChannels < 3)
188 return false;
189
190 std::pmr::vector<Vec3Type> vertices(GetDefaultMemoryResource());
191
192 // TODO: do we want to use indices of the channel map?
194
196
197 // Since at the moment ChannelMap doesn't support speakers on the bottom,
198 // we at least need to add a dummy speaker there for a better topology
199 // of the convex hull.
200 dummySpeakers.AddDummy(Vec3Type(0.0f, -1.0f, 0.0f));
201
202 const int numTopChannels = [channelMap]()
203 {
204 int numTopChannels = 0;
205 channelMap.ForEachChannel([&numTopChannels](EChannel channel)
206 {
208 });
209 return numTopChannels;
210 }();
211
212 // For more info about this see comments in LUTBuilder3D constructor
213 if (numTopChannels == 6 || numTopChannels == 4)
214 {
215 dummySpeakers.AddIfChannelNotPresent(EChannel::TopCenter);
216 }
217 else
218 {
220 }
221
222 if (TriangulateSpeakerLayout(std::span<const Vec3Type>(vertices), outIndices))
223 {
224 outVertices = std::move(vertices);
225 return true;
226 }
227 else
228 {
229 return false;
230 }
231 }
232 } // namespace SpeakerTriangulation
233
234 namespace VBAP
235 {
236 //======================================================================
238 {
239 float Angle;
240 uint32 ChannelId; // Channel index or identifier
241
242 // Operators necessary for sorting
243 [[nodiscard]] JPL_INLINE constexpr std::strong_ordering operator<=>(const ChannelAngle& other) const noexcept
244 {
245 const float a1 = Angle < 0.0f ? Angle + JPL_TWO_PI : Angle;
246 const float a2 = other.Angle < 0.0f ? other.Angle + JPL_TWO_PI : other.Angle;
247 if (a1 < a2) return std::strong_ordering::less;
248 if (a1 > a2) return std::strong_ordering::greater;
249 return std::strong_ordering::equal;
250 }
251 [[nodiscard]] JPL_INLINE constexpr bool operator==(const ChannelAngle& other) const noexcept { return Math::Abs(Angle - other.Angle) < 1e-6f; }
252
254 template<template<typename...> class ArrayType, class ...Args>
255 static inline void GetSortedChannelAngles(
258 std::function<float(EChannel)> getChannelAngle,
259 bool skipLFE = true)
260 {
261 sortedChannelAngles.clear();
262 sortedChannelAngles.reserve(channelMap.GetNumChannels() - skipLFE * channelMap.HasLFE());
263
265 {
266 // We don't use LFE for panning
267 if (skipLFE && channel == EChannel::LFE)
268 return;
269
270 // Top channels of the source don't participate in VBAP
272 return;
273
274 // We don't process LFE in our panning,
275 // but we need contiguous indices.
280 if (channel > EChannel::LFE && skipLFE && channelMap.HasLFE())
281 channelIndex--;
282
283 const float channelAngle = getChannelAngle(channel);
284
285 sortedChannelAngles.emplace_back(channelAngle < 0.0f ? channelAngle + JPL_TWO_PI : channelAngle, channelIndex);
286 });
287
288 std::ranges::sort(sortedChannelAngles);
289 }
290 };
291 } // namespace VBAP
292} // namespace JPL
#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
Definition ChannelMap.h:150
Definition ConvexHullBuilder.h:57
Utility class to encapsulate dummy speaker handling while building a LUT.
Definition DummySpeakers.h:36
JPL_INLINE constexpr auto Abs(const T &value) noexcept
Standard abs is not constexpr in C++20.
Definition Math.h:87
bool GetSpeakerVectors(ChannelMap channelMap, Vec3ContainerType &outVectors)
Definition VBAPEx.h:92
bool TriangulateSpeakerLayout(std::span< const Vec3Type > speakerVectors, Vec3iContainerType &outIndices)
Definition VBAPEx.h:138
std::array< uint8, 3 > Vec3i
Definition VBAPEx.h:89
Definition AcousticMaterial.h:36
Vec3Type ComputeVBAP(const Vec3Type &sourceDirection, const Vec3Type &triPointA, const Vec3Type &triPointB, const Vec3Type &triPointC)
Definition VBAPEx.h:48
std::uint32_t uint32
Definition Core.h:311
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 simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Minimal 2x2 matrix interface.
Definition MinimalMat.h:35
static JPL_INLINE constexpr Mat2 FromColumns(const Vec2 &l1, const Vec2 &l2) noexcept
Definition MinimalMat.h:42
Minimal 3x3 matrix interface.
Definition MinimalMat.h:83
static JPL_INLINE constexpr Mat3 FromColumns(const Vec3 &l1, const Vec3 &l2, const Vec3 &l3) noexcept
Definition MinimalMat.h:90
Definition VBAPEx.h:238
JPL_INLINE constexpr std::strong_ordering operator<=>(const ChannelAngle &other) const noexcept
Definition VBAPEx.h:243
float Angle
Definition VBAPEx.h:239
uint32 ChannelId
Definition VBAPEx.h:240
JPL_INLINE constexpr bool operator==(const ChannelAngle &other) const noexcept
Definition VBAPEx.h:251
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:255
Definition MinimalVec2.h:29
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97