JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
SpatialManager.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"
32#if JPL_HAS_ENV_PROPAGATION
33#include "JPLSpatial/Services/EnvironmentService.h"
34#include "JPLSpatial/Environment.h"
35#endif
37
38
39#include <algorithm>
40#include <cmath>
41#include <functional>
42#include <optional>
43#include <memory>
44#include <ranges>
45#include <span>
46#include <vector>
47#include <unordered_set>
48#include <memory_resource>
49
50#if 1
51namespace JPL
52{
53#ifdef JPL_TEST
55#endif
56} // namespace JPL
57
58namespace JPL::Spatial
59{
62
65
68
70 {
71 // TODO: decide if we want to cache some of the parameters (occlusion, etc.) per source, or per object
72
75
78
81
84
85 //? not used yet
86 // Object this source should be associated to,
87 // or 0, to associate this source to a new object.
89
91 PanEffectParameters PanParameters{ .Focus = 0.0f, .Spread = 1.0f };
92
94 std::optional<float> ObjectSize;
95
98
101 };
102
103 template<CVec3 Vec3Type>
105 {
107#if JPL_HAS_ENV_PROPAGATION
108 RoomId Room;
109#endif
111 };
112
113 // At each position in space relative to listener we have:
115 {
116 float Distance;
118
121
122 //float AirAbsorptionFilterValue; //? probably this doesn't belong her, since it is next level. i.e. computed from Distance
123 };
124
125 //? not used yet
126 template<CVec3 Vec3Type>
128 {
131
132 // Data computed for the object position
134 };
135
136 template<CVec3 Vec3Type>
138 {
141
143
144#if JPL_HAS_ENV_PROPAGATION
145 RoomId Room; //< Room this source is currently in
146#endif
149
150 std::optional<float> ObjectSize;
151
152 // std::vector<ReflectionId> ReflectionImageSources;
153 // std::vector<DifractionId> DifractionSources;
154
155 // TODO: this could be treated as main distance volume attenuation curve to use for indirect paths
157
159 };
160
161 //==========================================================================
164 template<CVec3 Vec3Type, class VBAPTraits = VBAPBaseTraits<Vec3Type>>
166 {
167 private:
168 /*static */JPL_INLINE SourceId NewSourceId() { return SourceId::New(); }
169
170 public:
171 // Alias to override allocator for the internal FlatMap we use
172 template<class Key, class T>
174
175 template<class Key>
176 using SetType = std::pmr::unordered_set<Key, std::hash<Key>, std::equal_to<Key>>;
177
178 public:
179 SpatialManager() = default;
180 ~SpatialManager() = default;
181
184
185 // Evaluate all the updated parametesr to all the spatial effects.
186 // Process sound propagation simulation.
187
188 JPL_INLINE const DirectPathService& GetDirectPathService() const { return mDirectPathService; }
189 JPL_INLINE DirectPathService& GetDirectPathService() { return mDirectPathService; }
190 JPL_INLINE const PanningService<VBAPTraits>& GetPanningService() const { return mPanningService; }
192
197
198 JPL_INLINE std::span<const SourceId> GetLastUpdatedSource() const;
199
202
205
207 JPL_INLINE ListenerId GetDefaultListener() const { return mDefaultListener; }
208
213 JPL_INLINE std::optional<Position<Vec3Type>> GetListenerPosition(ListenerId listener) const;
214
218
222
223 static JPL_INLINE float GetSpreadFromSourceSize(float sourceSize, float distance);
224
225 JPL_INLINE DirectEffectHandle GetDirectEffectHandle(SourceId source) const { return mSourceStuff.at(source).DirectEffectHandle; }
226 JPL_INLINE PanEffectHandle GetPanEffectHandle(SourceId source) const { return mSourceStuff.at(source).PanEffectHandle; }
227
231
233
239 // TODO: alternatively, we could employ SteamAudio's approach of feeding in buffer and applying all the processing internally
240
241 // Process audio for a source based on data computed during last call to AdvanceSimulation()
243 //void ProcessSourceBuffer(SourceId source, std::span<const float> inAudio, std::span<float> outAudio);
244
245 private:
246#ifdef JPL_TEST
248#endif
249
250 private:
251 // Services
252 DirectPathService mDirectPathService;
253 PanningService<VBAPTraits> mPanningService;
254#if JPL_HAS_ENV_PROPAGATION
256#endif
257 // TODO: we may or may not whant flat set instead of std::unordered_set
258
259 // Sources to be updated at next call to AdvanceSimulation()
261#if JPL_HAS_ENV_PROPAGATION
264#endif
265 // Object that have changed during last update.
266 // User or mixing engine may need this information.
267 std::pmr::vector<SourceId> mLastUpdatedSources{ GetDefaultMemoryResource() };
268#if JPL_HAS_ENV_PROPAGATION
269 std::pmr::vector<SourceId> mLastSourceRoomsChanged{ GetDefaultMemoryResource() };
270 std::pmr::vector<ListenerId> mLastListenerRoomsChanged{ GetDefaultMemoryResource() };
271#endif
272
273 // Per source data
275
276 // Intermediate data processed for the position the sources were at
277 // at the last call to AdvanceSimulation()
278 FlatMapType<SourceId, PositionData> mPositionData{ GetDefaultMemoryResource() }; // TODO: this is weird, it assumes single listener per source
279
280 // Default listener
281 const ListenerId mDefaultListener = ListenerId::New();
282
283 // Map of listeners
285 { {
286 { mDefaultListener, ListenerMock<Vec3Type>{
287 .Id = mDefaultListener,
288 .Position = {
289 .Location = Vec3Type(0, 0, 0),
291 }}
292 }
294 };
295
296} // namespace JPL::Spatialziation
297
298//==============================================================================
299//
300// Code beyond this point is implementation detail...
301//
302//==============================================================================
303namespace JPL::Spatial
304{
305 template<CVec3 Vec3Type, class VBAPTraits>
307 {
308 // TODO: before processing distance attenuation we need to process propagation paths,
309 // since sources may not have direct path, also we need to process distance attenuation
310 // for indirect paths and reflections
311
312 // Remove dirty sources that don't have listeners from the path update list
313 std::erase_if(mDirtySources, [this](SourceId source)
314 {
315 return !mListeners.contains(mSourceStuff[source].Listener);
316 });
317
318 //? we do still want to know which room each source is in,
319 // in case it gets a new listener
321 /*std::erase_if(mSourcesMoved, [this](SourceId source)
322 {
323 return !mListeners.contains(mSourceStuff[source].Listener);
324 });*/
325
326
327#if JPL_HAS_ENV_PROPAGATION
328 // Update listener room containment
329 {
332
334 {
336
337 const RoomId room = mEnvironmentService.GetRoomAt(listenerData.Position.Location);
338 if (listenerData.Room != room)
339 {
340 listenerData.Room = room;
341
342 // TODO: notify user/mixing engine?
343
345 }
346 }
347 }
348
349 // Update source room containment
350 {
353
355 {
356 SourceData<Vec3Type>& sourceData = mSourceStuff[source];
357
358 const RoomId room = mEnvironmentService.GetRoomAt(sourceData.Position.Location);
359 if (sourceData.Room != room)
360 {
361 sourceData.Room = room;
362 // TODO: notify user/mixing engine?
363
364 // Store sources that moved roomes during this iteration to let user
365 // update audio bus routing and mixing
367 }
368 }
369 }
370#endif
371
372 // Update source positional data
373 for (SourceId source : mDirtySources)
374 {
375 const SourceData<Vec3Type>& sourceData = mSourceStuff[source];
376
377 const auto& sourcePosition = sourceData.Position;
378 const auto& listenerPosition = mListeners[sourceData.Listener].Position;
379
380 // TODO: process rooms & portals, and indirect paths,
381 // update transmission loss and diffractoin path
382 /*
383 1. Get all portals of the room
384 2. Get the ones between source and listener
385 3. Process all diffraction paths
386 */
387
389 mDirectPathService.ProcessDirectPath(
392 );
393
394 // Cache direct path data
395 auto& positionData = mPositionData[source];
396 positionData.Distance = directPathResult.Distance;
397 positionData.DirectionDot = directPathResult.DirectionDot;
398
399 // Attenuation is a higher level API, depends on AttenuationCurve,
400 // we can't assume what curve user wants to use.
401 // But we can preprocess all curves associated with this source.
402 // TODO: mabye we could batch process multiple distances/sources per curve instead, if they are sharing curves?
403 mDirectPathService.EvaluateDistance(sourceData.DirectEffectHandle, directPathResult.Distance);
404
405 // Calculate cone attenuatoin factor
406 mDirectPathService.EvaluateDirection(sourceData.DirectEffectHandle, directPathResult.InvDirectionDot); // TODO: why are we evaluating source cone attenuation, but not listener's?
407
408 // TODO: can we eliminate this branch?
409 if (sourceData.ObjectSize)
410 {
411 const float spread = GetSpreadFromSourceSize(*sourceData.ObjectSize, directPathResult.Distance);
412 mPanningService.SetPanningEffectSpread(sourceData.PanEffectHandle, spread);
413 }
414
415 // TODO: this is not very useful for indirect paths that don't use spread and focus
416 // - in such case a different panning method needed
417
418 mPanningService.EvaluateDirection(sourceData.PanEffectHandle, directPathResult.Position, sourceData.SpatializationType);
419 }
420
421 // Store sources updated during this iteration to let user check
422 // which sourced were actually changed
423 mLastUpdatedSources.resize(mDirtySources.size());
424 std::ranges::copy(mDirtySources, mLastUpdatedSources.begin());
425
426 mDirtySources.clear();
427#if JPL_HAS_ENV_PROPAGATION
428 mSourcesMoved.clear();
429#endif
430 }
431
432 template<CVec3 Vec3Type, class VBAPTraits>
434 {
435 return mLastUpdatedSources;
436 }
437
438 template<CVec3 Vec3Type, class VBAPTraits>
440 {
441 const auto listenerId = ListenerId::New();
442 mListeners.emplace(listenerId,
444 .Id = listenerId,
445 .Position = {
446 .Location = Vec3Type(0, 0, 0),
448 }
449 });
450 return listenerId;
451 }
452
453 template<CVec3 Vec3Type, class VBAPTraits>
455 {
456 if (!mListeners.contains(listener) || listener == mDefaultListener)
457 return false;
458
459#if JPL_HAS_ENV_PROPAGATION
461#endif
462 mListeners.erase(listener);
463
464 // TODO: we may or may not want to assign default listener to orphan sources
465 return true;
466 }
467
468 template<CVec3 Vec3Type, class VBAPTraits>
470 {
471 // Source has to be initialized with target channel count
472 JPL_ASSERT(options.NumTargetChannels > 0);
473
474 const SourceId newId = NewSourceId();
475
477 std::span<const ChannelMap> targetChannelMaps = targetChannelMap.IsValid()
478 ? std::span<const ChannelMap>(&targetChannelMap, 1)
479 : std::span<const ChannelMap>{};
480
482 {
483 .BaseCurve = options.DistanceAttenuationCurve,
484 .AttenuationCone = options.AttenuationCone
485 };
486
488 {
489 .SourceChannelMap = ChannelMap::FromNumChannels(options.NumChannels),
490 .TargetChannelMaps = targetChannelMaps,
491 .EffectParameters = options.PanParameters
492 };
493
494 mSourceStuff.emplace(newId, SourceData<Vec3Type>{
495 .Id = newId,
496 .Listener = options.ListenerId.IsValid() ? options.ListenerId : mDefaultListener,
497 .DirectEffectHandle = mDirectPathService.InitializeDirrectEffect(std::move(directEffectParameters)),
498 .PanEffectHandle = mPanningService.InitializePanningEffect(std::move(panEffectParameters)),
499 .ObjectSize = options.ObjectSize,
500 .AttenuationCurve = options.DistanceAttenuationCurve,
501 .SpatializationType = options.SpatializationType
502 });
503
504 mPositionData.emplace(newId,
506 .Distance = 0.0f,
507 .DirectionDot = 1.0f,
508 .Obstruction = 0.0f,
509 .Occlusion = 0.0f
510 });
511
512 // If number of source channel specified, initialize panning data
513 // TODO: make panning initialization optional, as per user flags?
514
515 return newId;
516 }
517
518 template<CVec3 Vec3Type, class VBAPTraits>
520 {
521 if (!source.IsValid())
522 return false;
523
524 if (mSourceStuff.contains(source))
525 {
526 const auto& sourceData = mSourceStuff[source];
527 mDirectPathService.ReleaseEffectData(sourceData.DirectEffectHandle);
528 mPanningService.ReleasePanningEffect(sourceData.PanEffectHandle);
529 }
530
531 mSourceStuff.erase(source);
532 mPositionData.erase(source);
533 mDirtySources.erase(source);
534#if JPL_HAS_ENV_PROPAGATION
535 mSourcesMoved.erase(source);
536#endif
537 return true;
538 }
539
540 template<CVec3 Vec3Type, class VBAPTraits>
542 {
543 if (!source || !listener)
544 return false;
545
546 if (!mListeners.contains(listener))
547 return false;
548
549 mSourceStuff.at(source).Listener = listener;
550 return true;
551 }
552
553 template<CVec3 Vec3Type, class VBAPTraits>
555 {
556 auto it = mListeners.find(listener);
557 if (it == mListeners.end())
558 return false;
559
560 auto& foundListener = it->second;
561 if (foundListener.Position != newPosition)
562 {
563 foundListener.Position = newPosition;
564
565 // Flag relevant sourced for the update
566 for (const auto& [sourceId, sourceData] : mSourceStuff)
567 {
568 if (sourceData.Listener == listener)
569 mDirtySources.insert(sourceId);
570 }
571#if JPL_HAS_ENV_PROPAGATION
573#endif
574 }
575
576 return true;
577 }
578
579 template<CVec3 Vec3Type, class VBAPTraits>
581 {
582 auto it = mListeners.find(listener);
583 if (it != mListeners.end())
584 return it->second.Position;
585 return {};
586 }
587
588 template<CVec3 Vec3Type, class VBAPTraits>
590 {
591 auto it = mSourceStuff.find(source);
592 if (it == mSourceStuff.end())
593 return false;
594
595 auto& foundSource = it->second;
596 if (foundSource.Position != newPosition)
597 {
598 foundSource.Position = newPosition;
599 mDirtySources.insert(source);
600#if JPL_HAS_ENV_PROPAGATION
601 mSourcesMoved.insert(source);
602#endif
603 }
604
605 return true;
606 }
607
608 template<CVec3 Vec3Type, class VBAPTraits>
610 {
611 return mPanningService.SetPanningEffectParameters(
612 GetPanEffectHandle(source),
614 }
615
616 template<CVec3 Vec3Type, class VBAPTraits>
618 {
619 if (distance <= 0.0f)
620 return 1.0f;
621
622 return std::atan((0.5f * sourceSize) / distance) * JPL_INV_PI;
623 }
624
625 template<CVec3 Vec3Type, class VBAPTraits>
627 {
628 return mDirectPathService.GetDistanceAttenuation(mSourceStuff.at(source).DirectEffectHandle, curve);
629 }
630
631 template<CVec3 Vec3Type, class VBAPTraits>
633 {
634 return mDirectPathService.GetDirectionAttenuation(mSourceStuff.at(source).DirectEffectHandle);
635 }
636
637 template<CVec3 Vec3Type, class VBAPTraits>
639 {
640 return mPanningService.GetChannelGainsFor(mSourceStuff.at(source).PanEffectHandle, targetChannelMap);
641 }
642
643} // JPL::Spatial
644
645#endif
#define JPL_ASSERT(inExpression,...)
Main assert macro, usage: JPL_ASSERT(condition, message) or JPL_ASSERT(condition)
Definition ErrorReporting.h:76
Definition ChannelMap.h:150
static constexpr ChannelMap FromNumChannels(uint32 numChannels)
Definition ChannelMap.h:199
Definition DirectPathService.h:84
Definition FlatMap.h:51
Definition PanningService.h:136
Definition SpatialManager.h:166
JPL_INLINE bool DeleteSource(SourceId source)
Definition SpatialManager.h:519
JPL_INLINE std::span< const float > GetChannelGains(SourceId source, ChannelMap targetChannelMap) const
Definition SpatialManager.h:638
JPL_INLINE std::optional< Position< Vec3Type > > GetListenerPosition(ListenerId listener) const
Definition SpatialManager.h:580
JPL_INLINE void AdvanceSimulation()
Definition SpatialManager.h:306
JPL_INLINE PanningService< VBAPTraits > & GetPanningService()
Definition SpatialManager.h:191
static JPL_INLINE float GetSpreadFromSourceSize(float sourceSize, float distance)
Definition SpatialManager.h:617
JPL_INLINE PanEffectHandle GetPanEffectHandle(SourceId source) const
Definition SpatialManager.h:226
JPL_INLINE float GetConeAttenuation(SourceId source) const
Definition SpatialManager.h:632
JPL_INLINE const DirectPathService & GetDirectPathService() const
Definition SpatialManager.h:188
JPL_INLINE std::span< const SourceId > GetLastUpdatedSource() const
Definition SpatialManager.h:433
JPL_INLINE const PanningService< VBAPTraits > & GetPanningService() const
Definition SpatialManager.h:190
JPL_INLINE DirectPathService & GetDirectPathService()
Definition SpatialManager.h:189
JPL_INLINE SourceId CreateSource(const SourceInitParameters &options)
Definition SpatialManager.h:469
JPL_INLINE bool SetSourceFocusAndSpread(SourceId source, PanEffectParameters focusAndSpread)
Definition SpatialManager.h:609
JPL_INLINE ListenerId CreateListener()
Definition SpatialManager.h:439
JPL_INLINE bool SetListenerPosition(ListenerId listener, const Position< Vec3Type > &newPosition)
Definition SpatialManager.h:554
JPL_INLINE float GetDistanceAttenuation(SourceId source, const AttenuationCurveRef &curve) const
Definition SpatialManager.h:626
SpatialManager & operator=(const SpatialManager &)=delete
JPL_INLINE ListenerId GetDefaultListener() const
Definition SpatialManager.h:207
SpatialManager(const SpatialManager &)=delete
JPL_INLINE bool SetSourcePosition(SourceId source, const Position< Vec3Type > &newPosition)
Definition SpatialManager.h:589
JPL_INLINE DirectEffectHandle GetDirectEffectHandle(SourceId source) const
Definition SpatialManager.h:225
JPL_INLINE bool DeleteListener(ListenerId listener)
Definition SpatialManager.h:454
JPL_INLINE bool SetListener(SourceId source, ListenerId listener)
Definition SpatialManager.h:541
std::pmr::unordered_set< Key, std::hash< Key >, std::equal_to< Key > > SetType
Definition SpatialManager.h:176
Definition SpatialManager.h:59
IDType< ListenerMockIDTag > ListenerId
Definition SpatialManager.h:64
Definition AcousticMaterial.h:36
std::uint64_t uint64
Definition Core.h:312
std::shared_ptr< AttenuationFunction > AttenuationCurveRef
Definition DirectPathService.h:58
std::uint32_t uint32
Definition Core.h:311
std::pmr::memory_resource * GetDefaultMemoryResource() noexcept
Definition Memory.h:42
ESpatializationType
Determines spatialized source channel oritentation.
Definition PanningService.h:107
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
JPL_INLINE float GetSpreadFromSourceSize(float sourceSize, float distance)
Definition PannerBase.h:69
Definition DirectPathService.h:53
Definition DirectPathService.h:77
Definition DirectPathService.h:44
static constexpr IDType New() noexcept
Definition IDType.h:44
static JPL_INLINE OrientationData< Vec3Type > IdentityForward() noexcept
Identity facing forward as per right-handed rotation.
Definition Position.h:46
Definition PanningService.h:122
Definition PanningService.h:116
float Focus
Definition PanningService.h:117
Location and orientation in one place.
Definition Position.h:80
Definition SpatialManager.h:63
Definition SpatialManager.h:105
ListenerId Id
Definition SpatialManager.h:106
JPL::Position< Vec3Type > Position
Definition SpatialManager.h:110
Definition SpatialManager.h:66
Definition SpatialManager.h:128
PositionId Id
Definition SpatialManager.h:129
PositionData Data
Definition SpatialManager.h:133
JPL::Position< Vec3Type > Position
Definition SpatialManager.h:130
Definition SpatialManager.h:115
float Occlusion
Definition SpatialManager.h:120
float DirectionDot
Definition SpatialManager.h:117
float Obstruction
Definition SpatialManager.h:119
float Distance
Definition SpatialManager.h:116
Definition SpatialManager.h:138
ListenerId Listener
Definition SpatialManager.h:140
std::optional< float > ObjectSize
Definition SpatialManager.h:150
ESpatializationType SpatializationType
Definition SpatialManager.h:158
JPL::Position< Vec3Type > Position
Definition SpatialManager.h:142
AttenuationCurveRef AttenuationCurve
Definition SpatialManager.h:156
SourceId Id
Definition SpatialManager.h:139
JPL::DirectEffectHandle DirectEffectHandle
Definition SpatialManager.h:147
JPL::PanEffectHandle PanEffectHandle
Definition SpatialManager.h:148
Definition SpatialManager.h:70
uint32 NumTargetChannels
Number of target channels for panning. Must be greater than 0.
Definition SpatialManager.h:77
uint64 ObjectId
Definition SpatialManager.h:88
JPL::AttenuationCone AttenuationCone
Source directivity cone.
Definition SpatialManager.h:100
AttenuationCurveRef DistanceAttenuationCurve
Base distance attenuation curve.
Definition SpatialManager.h:97
uint32 NumChannels
Number of source channels. If 0, panning is not initialized for this source.
Definition SpatialManager.h:74
PanEffectParameters PanParameters
Pan parameters used if panning effect is initialized for the source.
Definition SpatialManager.h:91
std::optional< float > ObjectSize
If set, Spread value is calculated from ObjectSize based on distance to the listener.
Definition SpatialManager.h:94
ESpatializationType SpatializationType
Determines spatialized source channel oritentation.
Definition SpatialManager.h:83
JPL::Spatial::ListenerId ListenerId
Optional listener to assign this source to. If not set, default listener is used.
Definition SpatialManager.h:80
Definition SpatialManager.h:60
static constexpr std::size_t size() noexcept
Get number of element of the vector.
Definition SIMD.h:97