JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
PanningService.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
30
31#include <algorithm>
32#include <concepts>
33#include <span>
34#include <memory>
35#include <vector>
36#include <initializer_list>
37#include <utility>
38#include <memory_resource>
39
40namespace JPL
41{
46
49
51 {
54
55 [[nodiscard]] JPL_INLINE constexpr bool operator==(const PanningCacheKey& other) const
56 {
57 return Handle == other.Handle && TargetChannelMap == other.TargetChannelMap;
58 }
59 };
60
67
69 {
70 return lhs.SourceMap == rhs.SourceMap && lhs.TargetMap == rhs.TargetMap;
71 }
72
73
74} // namespace JPL
75
76namespace std
77{
78 static_assert(sizeof(size_t) >= sizeof(uint64_t));
79 static_assert(std::convertible_to<uint64_t, size_t>);
80
81 template <>
82 struct hash<JPL::SourceLayoutKey>
83 {
84 size_t operator()(const JPL::SourceLayoutKey& key) const
85 {
86 return (static_cast<uint64_t>(key.SourceMap.GetChannelMask()) << 32) | key.TargetMap.GetChannelMask();
87 }
88 };
89
90 template <>
91 struct hash<JPL::PanningCacheKey>
92 {
93 size_t operator()(const JPL::PanningCacheKey& key) const
94 {
95 // Sanity check that handle is some kind of 32-bit int that we can just bit-shift pack
96 static_assert(sizeof(key.Handle) == 4);
97 return (static_cast<uint64_t>(std::hash<JPL::PanEffectHandle>{}(key.Handle)) << 32) | key.TargetChannelMap.GetChannelMask();
98 }
99 };
100}
101
102namespace JPL
103{
104 //==========================================================================
107 {
108 None,
109 Position,
111 };
112 // TODO: should we store spatialization type with the pan effect parameters,
113 // or keep it elsewhere since it shouldn't be dynamic?
114
116 {
117 float Focus;
118 float Spread;
119 };
120
122 {
123 ChannelMap SourceChannelMap; //< ChannelMap to initialize panning effect for
124 std::span<const ChannelMap> TargetChannelMaps; //< (required) Target ChannelMaps for the panning effect
125
126 PanEffectParameters EffectParameters //< Parameters controlling panning behavior
127 {
128 .Focus = 1.0f,
129 .Spread = 1.0f
130 };
131 };
132
133 //==========================================================================
134 template<class VBAPTraits>
136 {
137 public:
138 // Alias to override allocator for the internal FlatMap we use
139 template<class Key, class T>
141
142 using Vec3Type = typename VBAPTraits::Vec3Type;
144
149
150 public:
151 PanningService() = default;
152
155
156 // High level API to:
157 // - create panners for different kinds of channel maps and panning settings
158 // - get channel gains for sources
159 // ...
160
164
167
168 //==========================================================================
170
177
181
182 private:
186 JPL_INLINE bool AddSourceTargets(PanEffectHandle source, std::span<const ChannelMap> targetChannelMaps);
187 JPL_INLINE bool AddSourceTargets(PanEffectHandle source, std::initializer_list<const ChannelMap> targetChannelMaps);
188 public:
189
193
197
202
207
208 //==========================================================================
212
213 // Create panning data for channel map and optionally associate source with it.
214 // This can be used to initialize panning data for known channel maps,
215 // as well as to associate that data with sources, since the data only created if
216 // it doesn't exist yet in the cache.
217 // @returns panning data created for the channel map, which can be nullptr if failed to initialize
219
220 // @returns panning data for requrested channel map if it was previously created
221 // by calling CreatePanningDataFor
222 JPL_INLINE std::shared_ptr<const SourceLayout> GetPanningDataFor(SourceLayoutKey layout) const;
223
224 // Initialize channel gains for source channel map.
225 // Returned gains must be managed by the user.
226 // This overload is meant for the case when using Panners directly.
228
229 // Initialize channel gains for source. This is called internally in AddSourceTargets function.
230 // The gains are manaded by PanningService and used to cache results of the last call to EvaluateDirection
232
233 // @returns SourceLayout associated with the source if exists
234 JPL_INLINE std::shared_ptr<const SourceLayout> GetSourceLayoutFor(PanEffectHandle source) const;
235
236 private:
237 // TODO: do we need to clear these at any point?
238
242
243 //std::unordered_map<ChannelMap, std::shared_ptr<SourceLayout>> mInitializedSourceLayouts;
244 // TODO: maybe we could use number of channels everywhere instead of ChannelMap?
245 // In that case we could use simple static arrays here instead of maps
247 // TODO: we should probably delete unused/unreferenced source layouts at some point, right?
248
249 // Static association VBAPs with handles
251
252 // Dynamic parameters that can be changed at runtime
254
255 // Per target map - store channel mix map
256 // vector size = number of source channels * number of target channels
258 };
259} // namespace JPL
260
261
262
263//==============================================================================
264//
265// Code beyond this point is implementation detail...
266//
267//==============================================================================
268namespace JPL
269{
270 template<class VBAPTraits>
272 {
273 if (!targetChannelMap.IsValid())
274 return nullptr;
275
276 auto& panner = mPanners[targetChannelMap];
277 if (!panner.IsInitialized())
278 {
279 if (!panner.InitializeLUT(targetChannelMap))
280 {
281 mPanners.erase(targetChannelMap);
282 return nullptr;
283 }
284 }
285 return &panner;
286 }
287
288 template<class VBAPTraits>
290 {
291 auto it = mPanners.find(targetChannelMap);
292 return it != mPanners.end() ? &(it->second) : nullptr;
293 }
294
295 template<class VBAPTraits>
297 -> std::shared_ptr<const SourceLayout>
298 {
299 if (!layout.SourceMap.IsValid())
300 return nullptr;
301
302 auto& sourceLayout = mInitializedSourceLayouts[layout];
303
304 if (!sourceLayout)
305 {
307
308 // We have to initialize source for a specific target output channel layout
309 const auto* panner = CreatePannerFor(layout.TargetMap);
310
311 if (!panner || !panner->InitializeSourceLayout(layout.SourceMap, *sourceLayout))
312 {
313 JPL_ERROR_TAG("PanningService", "Failed to initialize SourceLayout");
315 mInitializedSourceLayouts.erase(layout);
316 }
317 }
318
319 if (source.IsValid() && sourceLayout)
320 mSourceLayouts[source] = sourceLayout;
321
322 return sourceLayout;
323 }
324
325 template<class VBAPTraits>
326 JPL_INLINE auto PanningService<VBAPTraits>::GetPanningDataFor(SourceLayoutKey layout) const -> std::shared_ptr<const SourceLayout>
327 {
328 auto sourceLayout = mInitializedSourceLayouts.find(layout);
329 if (sourceLayout != mInitializedSourceLayouts.end())
330 return sourceLayout->second;
331
332 return nullptr;
333 }
334
335 template<class VBAPTraits>
338 std::pmr::vector<float>& outChannelGains) const
339 {
340 if (!targetChannelMap.IsValid() || !sourceChannelMap.IsValid())
341 return false;
342
343 outChannelGains.resize(sourceChannelMap.GetNumChannels() * targetChannelMap.GetNumChannels());
344 std::ranges::fill(outChannelGains, 0.0f);
345
346 return true;
347 }
348
349 template<class VBAPTraits>
351 {
352 if (!targetChannelMap.IsValid() || !source.IsValid() || !mSourceLayouts.contains(source))
353 return false;
354
355 // TODO: do we want to ensure existance of VBAP association for this source?
356 // We could also try decoupling source channel map from source, and allow multiple source channel maps per source (?)
357
358 // Ensure we have gain arrays for each channel of the source
359 auto& channelGains = mPanningCache[
361 .Handle = source,
362 .TargetChannelMap = targetChannelMap
363 }];
364
365 channelGains.resize(mSourceLayouts[source]->ChannelGroups.size() * targetChannelMap.GetNumChannels());
366 std::ranges::fill(channelGains, 0.0f);
367
368 return true;
369 }
370
371 template<class VBAPTraits>
373 -> std::span<const float>
374 {
375 auto cache = mPanningCache.find(
377 .Handle = source,
378 .TargetChannelMap = targetChannelMap
379 });
380
381 if (cache != mPanningCache.end())
382 return cache->second;
383
384 return {};
385 }
386
387 template<class VBAPTraits>
389 {
390 if (!initParameters.SourceChannelMap.IsValid())
391 return {};
392
393 if (initParameters.TargetChannelMaps.empty())
394 return {};
395
396 const auto newHandle = PanEffectHandle::New();
397
398 // Create panning data for each source->target pair requirested for this source
399 for (const ChannelMap& targetMap : initParameters.TargetChannelMaps)
400 {
401 std::shared_ptr<const SourceLayout> panningData =
402 CreatePanningDataFor(
404 .SourceMap = initParameters.SourceChannelMap,
405 .TargetMap = targetMap
406 }, newHandle);
407
408 if (!panningData)
409 {
410 ReleasePanningEffect(newHandle);
411 return {};
412 }
413 }
414
415 if (AddSourceTargets(newHandle, initParameters.TargetChannelMaps))
416 {
417 mPanningParams[newHandle] = initParameters.EffectParameters;
418
419 return newHandle;
420 }
421 else
422 {
423 // If we failed to initialize taret data,
424 // we need to clear other data that might have been initialized
425 ReleasePanningEffect(newHandle);
426 }
427
428 return {};
429 }
430
431 template<class VBAPTraits>
433 {
434 return mSourceLayouts.erase(source)
435 + mPanningParams.erase(source)
436 + mPanningCache.erase_if([source](const std::pair<const PanningCacheKey, std::pmr::vector<float>>& pair)
437 {
438 return pair.first.Handle == source;
439 });
440 }
441
442 template<class VBAPTraits>
444 {
445 if (!source.IsValid())
446 return false;
447
448 if (!mSourceLayouts.contains(source))
449 {
450 JPL_ERROR_TAG("PanningService", "AddSoruceTargets failed because source handle wasn't initialized before adding source targets. "
451 "This is likely because the provided source handle was released previously and is no longer valid.");
452 return false;
453 }
454
455 if (targetChannelMaps.empty())
456 return true;
457
458 bool hasAnyTargetInitialized = false;
459
460 for (ChannelMap channelMap : targetChannelMaps)
461 {
462 hasAnyTargetInitialized |= CreateChannelGainsFor(channelMap, source);
463 }
464
466 }
467
468 template<class VBAPTraits>
469 JPL_INLINE bool PanningService<VBAPTraits>::AddSourceTargets(PanEffectHandle source, std::initializer_list<const ChannelMap> targetChannelMaps)
470 {
471 return AddSourceTargets(source, std::span(targetChannelMaps));
472 }
473
474 namespace Impl
475 {
476 // If the source direction is pointing directly up, bias towards forward
477 template<CVec3 Vec3Type>
478 JPL_INLINE Vec3Type SanitizeSourceDir(const Vec3Type sourceDirection)
479 {
480 using Float = Vec3FloatType<Vec3Type>;
481
482 // TODO: This is a substantial offset, maybe we could revise some of the rotaion math to fall back to forward without needing this
483 static const Vec3Type fallbackDir = Normalized(Vec3Type(0, 1, -static_cast<Float>(5e-2))); // bias towards 2D forward (3D Z -> 2D Y axis)
484
487 static_cast<Float>(1.0))
490
491 SetY(sanitizedDirection, std::copysign(GetY(sanitizedDirection), GetY(sourceDirection))); // We need to preserve the sign
492 return sanitizedDirection;
493 }
494 }
495
496 template<class VBAPTraits>
498 {
499 if (!source.IsValid())
500 return false;
501
502 // Assert Location is a normalized relative unit length direction vector
503 JPL_ASSERT(Math::IsNearlyEqual(LengthSquared(position.Location), 1.0f));
504
505 for (auto&& [key, gains] : mPanningCache)
506 {
507 if (key.Handle == source && !gains.empty())
508 {
509 JPL_ASSERT(mSourceLayouts.contains(key.Handle));
510 JPL_ASSERT(mPanners.contains(key.TargetChannelMap));
511
512 const auto& sourceLayout = mSourceLayouts[key.Handle];
513 const auto& params = mPanningParams[key.Handle];
514
516
517 // TODO: move this switch statement outside of the loop
518 switch (spatialziationType)
519 {
521 {
522 // TODO: we need to still do basic direct gain assignment
523 JPL_ASSERT(false, "Not implemented.");
524 }
525 break;
527 {
528 const PanData updateData
529 {
530 .SourceDirection = Impl::SanitizeSourceDir(position.Location), // location is direction
531 .Focus = params.Focus,
532 .Spread = params.Spread,
533 };
534 mPanners[key.TargetChannelMap].ProcessVBAPData(*sourceLayout, updateData, gains);
535 }
536 break;
538 {
540 {
541 .Pan = {
542 .SourceDirection = Impl::SanitizeSourceDir(position.Location),
543 .Focus = params.Focus,
544 .Spread = params.Spread
545 },
546 .Orientation = position.Orientation // TODO: orientation is actually up and forward
547 };
548 mPanners[key.TargetChannelMap].ProcessVBAPData(*sourceLayout, updateData, gains);
549 }
550 break;
551 default:
552 JPL_ASSERT(false, "Should be unreachable");
553 break;
554 }
555 }
556 }
557
558 return true;
559 }
560
561 template<class VBAPTraits>
562 JPL_INLINE auto PanningService<VBAPTraits>::GetSourceLayoutFor(PanEffectHandle source) const -> std::shared_ptr<const SourceLayout>
563 {
564 auto data = mSourceLayouts.find(source);
565 return data == mSourceLayouts.end() ? nullptr : data->second;
566 }
567
568 template<class VBAPTraits>
570 {
571 if (!effect.IsValid())
572 return false;
573
574 mPanningParams[effect] = parameters;
575 return true;
576 }
577
578 template<class VBAPTraits>
580 {
581 if (!effect.IsValid())
582 return false;
583
584 mPanningParams[effect].Spread = spread;
585 return true;
586 }
587
588} // namespace JPL
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
#define JPL_ERROR_TAG(tag, message)
Definition ErrorReporting.h:124
Definition ChannelMap.h:150
constexpr uint32 GetChannelMask() const
Definition ChannelMap.h:194
Definition FlatMap.h:51
Definition PanningService.h:136
JPL_INLINE std::shared_ptr< const SourceLayout > GetPanningDataFor(SourceLayoutKey layout) const
Definition PanningService.h:326
JPL_INLINE const PannerType * CreatePannerFor(ChannelMap targetChannelMap)
Definition PanningService.h:271
JPL_INLINE bool CreateChannelGainsFor(ChannelMap targetChannelMap, ChannelMap sourceChannelMap, std::pmr::vector< float > &outChannelGains) const
Definition PanningService.h:336
JPL_INLINE bool CreateChannelGainsFor(ChannelMap targetChannelMap, PanEffectHandle source)
Definition PanningService.h:350
JPL_INLINE bool EvaluateDirection(PanEffectHandle source, const Position< Vec3Type > &position, ESpatializationType spatialziationType)
Definition PanningService.h:497
typename PannerType::SourceLayoutType SourceLayout
Definition PanningService.h:146
typename PannerType::PanUpdateData PanData
Definition PanningService.h:147
typename VBAPTraits::Vec3Type Vec3Type
Definition PanningService.h:142
JPL_INLINE std::span< const float > GetChannelGainsFor(PanEffectHandle source, ChannelMap targetChannelMap) const
Definition PanningService.h:372
JPL_INLINE bool SetPanningEffectSpread(PanEffectHandle effect, float spread)
Definition PanningService.h:579
PanningService()=default
PanningService & operator=(const PanningService &)=delete
VBAPTraits PannerTraits
Definition PanningService.h:143
JPL_INLINE bool SetPanningEffectParameters(PanEffectHandle effect, const PanEffectParameters &parameters)
Definition PanningService.h:569
typename PannerType::PanUpdateDataWithOrientation PanDataWithOrientation
Definition PanningService.h:148
JPL_INLINE const PannerType * GetPannerFor(ChannelMap targetChannelMap) const
Definition PanningService.h:289
JPL_INLINE PanEffectHandle InitializePanningEffect(const PanEffectInitParameters &initParameters)
High level API.
Definition PanningService.h:388
PanningService(const PanningService &)=delete
JPL_INLINE std::shared_ptr< const SourceLayout > GetSourceLayoutFor(PanEffectHandle source) const
Definition PanningService.h:562
JPL_INLINE std::shared_ptr< const SourceLayout > CreatePanningDataFor(SourceLayoutKey layout, PanEffectHandle source={})
Definition PanningService.h:296
JPL_INLINE bool ReleasePanningEffect(PanEffectHandle source)
Definition PanningService.h:432
Definition PannerBase.h:227
typename PanType::SourceLayout SourceLayoutType
Definition PannerBase.h:303
JPL_INLINE constexpr bool IsNearlyEqual(T a, T b, T tolerance=JPL_FLOAT_EPS_V< T >) noexcept
Definition Math.h:152
JPL_INLINE constexpr auto Abs(const T &value) noexcept
Standard abs is not constexpr in C++20.
Definition Math.h:87
Definition AcousticMaterial.h:36
@ None
Definition DistanceAttenuation.h:34
JPL_INLINE void reset_pmr_shared(std::shared_ptr< T > &sharedPtr, Y *ptr)
Definition Memory.h:221
JPL_INLINE void SetY(Vec3Type &v, Vec3FloatType< Vec3Type > value) noexcept
Definition Vec3Traits.h:40
typename StandartPanner::SourceLayoutType StandartSourceLayout
Definition PanningService.h:43
typename StandartPanner::PanUpdateDataWithOrientation StandartPanDataWithOrientation
Definition PanningService.h:45
IDType< PanningServiceIDTag > PanEffectHandle
Definition PanningService.h:48
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
ESpatializationType
Determines spatialized source channel oritentation.
Definition PanningService.h:107
JPL_INLINE constexpr bool operator==(const Vec2 &A, const Vec2 &B) noexcept
Definition MinimalVec2.h:59
JPL_INLINE auto GetY(const Vec3Type &v) noexcept
Definition Vec3Traits.h:36
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
typename StandartPanner::PanUpdateData StandartPanData
Definition PanningService.h:44
Definition ChannelMap.h:268
static constexpr IDType New() noexcept
Definition IDType.h:44
Definition PanningService.h:122
PanEffectParameters EffectParameters
Definition PanningService.h:127
ChannelMap SourceChannelMap
Definition PanningService.h:123
std::span< const ChannelMap > TargetChannelMaps
Definition PanningService.h:124
Definition PanningService.h:116
float Spread
Definition PanningService.h:118
float Focus
Definition PanningService.h:117
Definition PanningService.h:51
JPL_INLINE constexpr bool operator==(const PanningCacheKey &other) const
Definition PanningService.h:55
ChannelMap TargetChannelMap
Definition PanningService.h:53
PanEffectHandle Handle
Definition PanningService.h:52
Definition PanningService.h:47
Location and orientation in one place.
Definition Position.h:80
Definition PanningService.h:62
ChannelMap SourceMap
Definition PanningService.h:63
ChannelMap TargetMap
Definition PanningService.h:64
Parameters to update VBAP data for a source.
Definition PannerBase.h:238
size_t operator()(const JPL::PanningCacheKey &key) const
Definition PanningService.h:93
size_t operator()(const JPL::SourceLayoutKey &key) const
Definition PanningService.h:84