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