VAE - Virtual Audio Engine 1
Small Data Driven Audio Engine
vae_voice_manager.hpp
Go to the documentation of this file.
1/**
2 * @file vae_voice_manager.hpp
3 * @author Tobias Kozel (t.kozel@pm.me)
4 * @brief Holds all voices and starts/stops them
5 * @version 0.1
6 * @date 2021-11-28
7 *
8 * @copyright Copyright (c) 2021
9 *
10 */
11
12#ifndef _VAE_VOICE_MANAGER
13#define _VAE_VOICE_MANAGER
14
15#include "./vae_util.hpp"
16
17#include "./vae_types.hpp"
18
19#include "./pod/vae_source.hpp"
20#include "./pod/vae_event.hpp"
21
22#include "./../../include/vae/vae.hpp"
27
28
29namespace vae { namespace core {
30 /**
31 * @brief There is only one voice pool and VAE and it's managed here.
32 * @details This class handles starting and stopping voices as well as virtualization
33 */
35 HeapBuffer<Voice> mFinishedVoiceQueue; ///< voices that finished playing are queued here
36 HeapBuffer<Voice> mVoices; ///< Currently playing voice are here
38 HeapBuffer<VoicePan> mVoicePans; ///< Interpolation data for the panning algorithm or manual panning data
39 HeapBuffer<VoiceFilter> mVoiceFiltered; ///< Data needed for filtering is here
40
41 Size mActiveVoices = 0; ///< Number of currently playing voices
42 Size mInactiveVoices = 0; ///< Number of currenly virtual voices
43 Size mActiveHRTFVoices = 0; ///< Number of ative hrtf voices
44 Size mHRTFVoiceCount = 0; ///< Number of voices reserved for hrtf
45 Size mHighestVoice = 0; ///< TODO bad
46 Size mFinishedPending = 0; ///< Voices in mFinishedVoiceQueue
47 Size mHighestFinishedVoice = 0; ///< TODO bad as well
48 Size mStarvedVoices = 0; ///< Voices which could not play since no other voice could be killed and are lost forever
49 Size mStoppedQueueOverflow = 0; ///< Voice which did not fit mFinishedVoiceQueue and could not triggered chained events
50 public:
51 Result init(const EngineConfig& config) {
52 VAE_PROFILER_SCOPE_NAMED("Voicemanager Init")
53 mVoices.resize(config.voices);
55 mVoicePans.resize(config.voices);
56 mVoiceFiltered.resize(config.voices);
57 mVirtualVoices.resize(config.virtualVoices);
59 return Result::Success;
60 }
61
63 return mVoices;
64 }
65
68 }
69
71 return mActiveVoices;
72 }
73
74 /**
75 * @brief Callback provided to iterate voices, needs to return
76 * a bool to indicate when a voice needs to be stopped.
77 * @tparam Func
78 * @param func
79 */
80 template <class Func>
81 void forEachVoice(const Func&& func) {
82 VAE_PROFILER_SCOPE_NAMED("Foreach Voice")
83 for (Size index = 0; index <= mHighestVoice; index++) {
84 auto& i = mVoices[index];
85 if (i.source == InvalidSourceHandle) { continue; }
86 if (!func(i, index)) {
87 stop(i); // stop the voice if callback returns false
88 }
89 }
93 }
94
95 template <class Func>
96 void forEachFinishedVoice(const Func&& func) {
97 VAE_PROFILER_SCOPE_NAMED("Foreach Finished Voice")
98 for (Size i = 0; i <= mHighestFinishedVoice; i++) {
99 auto& v = mFinishedVoiceQueue[i];
100 if (v.source == InvalidSourceHandle) { continue; }
101 if (!func(v)) { continue; };
103 v.source = InvalidSourceHandle; // now the finished voice is handled
104 }
109 }
110
112 return mVoicePans[index];
113 }
114
116 return mVoiceFiltered[index];
117 }
118
119 /**
120 * @brief
121 *
122 * @param event
123 * @param bank
124 * @param emitter
125 * @param mixer
126 * @return Result
127 */
129 Event& event, const BankHandle bank,
130 const Sample gain, const EmitterHandle emitter,
131 const ListenerHandle listener, const MixerHandle mixer
132 ) {
134
135 Size searchStartIndex;
136 Size searchEndIndex;
137 bool starved;
138
139 if (event.HRTF) {
140 searchStartIndex = 0;
141 searchEndIndex = mHRTFVoiceCount;
143 } else {
144 searchStartIndex = mHRTFVoiceCount;
145 searchEndIndex = (Size) mVoices.size();
146 starved = mActiveVoices == (mVoices.size() - mHRTFVoiceCount);
147 }
148
149 // No voices left, find one to virtualize
150 if(starved) {
151 VAE_PROFILER_SCOPE_NAMED("Search killable Voice")
152 Size potentialVirtual = ~0;
153 Sample lowestGain = 10;
154 for (Size i = searchStartIndex; i < searchEndIndex; i++) {
155 auto& v = mVoices[i];
156 if (v.critical) { continue; } // Don't kill important voices
157
158 if (!v.audible) {
159 if (makeVirtual(v) == Result::Success) {
160 searchStartIndex = i;
161 break;
162 }
163 }
164
165 if (v.gain <= lowestGain) {
166 lowestGain = v.gain;
167 potentialVirtual = i;
168 }
169 }
170
171 if (potentialVirtual < searchEndIndex) {
172 makeVirtual(mVoices[potentialVirtual]);
173 searchStartIndex = potentialVirtual;
174 }
175 }
176
177 // Find a free voice
178 for (Size i = searchStartIndex; i < searchEndIndex; i++) {
179 VAE_PROFILER_SCOPE_NAMED("Search Free Voice")
180 auto& v = mVoices[i];
181 if(v.source == InvalidSourceHandle) {
182 v = { }; // re init object resets time and so on
183 v.source = event.source;
184 v.event = event.id;
185 v.listener = listener;
186
187 // find out if voice should trigger events on end
188 // if not killing the voice is easier so this gets a flag
189 v.chainedEvents = v.chainedEvents || (event.on_end != InvalidEventHandle);
190
191 if (mixer != InvalidMixerHandle && !event.force_mixer) {
192 // Only use the mixer provided if it's valid
193 // and the event allows overriding it
194 v.mixer = mixer;
195 } else {
196 // Otherwise use the mixer from the event
197 v.mixer = event.mixer;
198 }
199
200 v.gain = event.gain * gain;
201 v.loop = event.loop;
202 v.attenuate = event.attenuate;
203 // v.filtered = true; // todo provide way to init the filter settings
204 if (v.filtered) {
205 mVoiceFiltered[i] = { };
206 }
207
208 v.emitter = emitter;
209 v.spatialized = event.spatial;
210 mVoicePans[i] = { }; // Always clear this since non spatial use this for manual panning
211 v.HRTF = event.HRTF;
212 v.bank = bank;
213 if (v.HRTF) {
215 } else {
217 }
222 VAE_DEBUG_VOICES("Started voice slot %i from event %i:%i\tactive: %i",
223 i, event.id, bank, mActiveVoices
224 )
225 return Result::Success;
226 }
227 }
228
229 mHighestVoice = (Size) mVoices.size() - 1;
230
233
234 VAE_DEBUG_VOICES("Voice starvation. Can't start voice from event %i:%i", event.id, bank)
235
237 }
238
239 /**
240 * @brief makes provided voice virtual
241 * @param v
242 * @return Result
243 */
246 for (Size i = 0; i < mVirtualVoices.size(); i++) {
247 auto& slot = mVirtualVoices[i];
248 if (slot.source != InvalidSourceHandle) { continue; }
249 slot = v;
252 if (slot.HRTF) {
254 } else {
256 }
260
261 VAE_DEBUG_VOICES("Virtualized voice from event %i:%i\tactive: %i\tincative: %i",
263 )
264 return Result::Success;
265 }
267 }
268
269 /**
270 * @brief Stops a voice. Mostly used internally since
271 * other stop function provide better usage
272 * also revives virtual voices
273 * @param v Voice to stop
274 * @return Result
275 */
278 if (v.source == InvalidSourceHandle) { return Result::Success; }
279
280 if (!v.chainedEvents) {
281 if (0 < mInactiveVoices) {
282 // If we have inactive voices, revive one
283 for (Size i = 0; i < mVirtualVoices.size(); i++) {
284 auto virt = mVirtualVoices[i];
285 if (virt.source == InvalidSourceHandle) { continue; }
286 if (virt.HRTF == v.HRTF) {
287 virt.started = false; // marks filter buffers for clearing
288 v = virt;
291 VAE_DEBUG_VOICES("Revived voice from event %i:%i\tactive: %i\tincative: %i",
293 )
297 return Result::Success;
298 }
299 }
300 }
301 v.source = InvalidSourceHandle; // Mark voice as free
302 if (v.HRTF) {
304 } else {
306 }
310 return Result::Success;
311 }
312
313 /**
314 * If the event triggers something on_end
315 * it needs to be added to the finishedVoiceQueue
316 * array in the voice manager.
317 * The update() function on the engine will handle it
318 */
319
320 // TODO VAE PERF
321 bool finished = false;
322 for (Size i = 0; i < mFinishedVoiceQueue.size(); i++) {
323 auto& f = mFinishedVoiceQueue[i];
324 if (f.source == InvalidSourceHandle) {
326 finished = true;
327 f = v;
328 // // This is set last since it marks the
329 // // finished voice for other threads
330 // f.source = v.source;
331 VAE_DEBUG_VOICES("Stopped voice from event %i:%i\tactive: %i",
332 f.event, f.bank, mActiveVoices
333 )
334 break;
335 }
336 }
337 if (v.HRTF) {
339 } else {
341 }
342 v.source = InvalidSourceHandle; // Mark voice as free
343
344 if (!finished) {
346 // Failed to find a free spot in finished voices array
347 // Event will be discarded
348 VAE_DEBUG_VOICES("finishedVoiceQueue is full. Stop Event %i in bank %i discarded", v.event, v.bank)
351 } else {
353 }
358 return Result::Success;
359 }
360
361 /**
362 * @brief Stop voice based on a member value and optionally an emitter
363 * @details Can kill multiple voices if they match. Alco affects virtual voices
364 * @tparam T Handle type like MixerHandle
365 * @param handle Handle to use to look for voice
366 * @param member Pointer to member variable of Voice
367 * @param emitter Optional emitter which also needs to match if provided
368 * @return Result Alsoways a success
369 */
370 template <typename T>
371 Result stop(T handle, T Voice::*member, const EmitterHandle emitter = InvalidEmitterHandle) {
373 // Kill all the virtual voices first
374 for (auto& v : mVirtualVoices) {
375 if (v.source == InvalidSourceHandle) { continue; }
376 // only consider active voices
377 if ((&v)->*member != handle) { continue; }
378 // If we got an emitter it has to match too
379 if (emitter != InvalidEmitterHandle && v.emitter == emitter) { continue; }
380 v.source = InvalidSourceHandle;
381 }
382
383 for (auto& v : mVoices) {
384 if (v.source == InvalidSourceHandle) { continue; }
385 if ((&v)->*member != handle) { continue; }
386 if (emitter != InvalidEmitterHandle && v.emitter == emitter) { continue; }
387 stop(v);
388 }
389 return Result::Success;
390 }
391
392 template <typename T>
393 void setVoiceProperty(EmitterHandle emitter, T Voice::*member, const T& value) {
395 for (auto& v : mVoices) {
396 if(v.emitter == emitter) {
397 (&v)->*member = value;
398 }
399 }
400 }
401
402 template <typename T>
403 void setVoiceProperty(EmitterHandle emitter, T VoiceFilter::*member, const T& value) {
405 for (Size i = 0; i < mVoices.size(); i++) {
406 auto& v = mVoices[i];
407 if(v.emitter == emitter) {
408 (&mVoiceFiltered[i])->*member = value;
409 v.filtered = true;
410 }
411 }
412 }
413 };
414
415 constexpr int _VAE_SIZE_VOICE_MANAGER = sizeof(VoiceManger);
416} } // vae::core
417
418#endif // _VAE_VOICE_MANAGER
Basically a bad std::vector without exceptions which can also work with foreign memory.
Definition: THeapBuffer.hpp:49
There is only one voice pool and VAE and it's managed here.
VoicePan & getVoicePan(Size index)
Result stop(T handle, T Voice::*member, const EmitterHandle emitter=InvalidEmitterHandle)
Stop voice based on a member value and optionally an emitter.
HeapBuffer< Voice > mVoices
Currently playing voice are here.
Size mHRTFVoiceCount
Number of voices reserved for hrtf.
Result stop(Voice &v)
Stops a voice.
Result play(Event &event, const BankHandle bank, const Sample gain, const EmitterHandle emitter, const ListenerHandle listener, const MixerHandle mixer)
void setVoiceProperty(EmitterHandle emitter, T VoiceFilter::*member, const T &value)
Size mHighestFinishedVoice
TODO bad as well.
Size mActiveHRTFVoices
Number of ative hrtf voices.
Size mActiveVoices
Number of currently playing voices.
void setVoiceProperty(EmitterHandle emitter, T Voice::*member, const T &value)
HeapBuffer< Voice > & all()
VoiceFilter & getVoiceFilter(Size index)
HeapBuffer< VoiceFilter > mVoiceFiltered
Data needed for filtering is here.
void forEachVoice(const Func &&func)
Callback provided to iterate voices, needs to return a bool to indicate when a voice needs to be stop...
void forEachFinishedVoice(const Func &&func)
Size mStarvedVoices
Voices which could not play since no other voice could be killed and are lost forever.
Size mFinishedPending
Voices in mFinishedVoiceQueue.
HeapBuffer< Voice > & finished()
HeapBuffer< Voice > mVirtualVoices
Result makeVirtual(Voice &v)
makes provided voice virtual
HeapBuffer< Voice > mFinishedVoiceQueue
voices that finished playing are queued here
Size mInactiveVoices
Number of currenly virtual voices.
Result init(const EngineConfig &config)
Size mStoppedQueueOverflow
Voice which did not fit mFinishedVoiceQueue and could not triggered chained events.
HeapBuffer< VoicePan > mVoicePans
Interpolation data for the panning algorithm or manual panning data.
T max(const T &v1, const T &v2)
Definition: TMath.hpp:21
const char *const voiceHRTFCount
const char *const starvedVoiceCount
const char *const voiceCount
Definition: vae_profiler.hpp:7
const char *const voiceVirtualCount
Definition: vae_profiler.hpp:9
const char *const stoppedVoiceOverflow
const char *const voiceFinishedCount
Definition: vae_profiler.hpp:8
constexpr int _VAE_SIZE_VOICE_MANAGER
Contains Typedefinitions and basic structures use by the public API and internally.
Definition: vae.hpp:31
constexpr EmitterHandle InvalidEmitterHandle
Definition: vae.hpp:61
unsigned int Size
How the elements are addressed in the heapbuffer.
Definition: vae.hpp:33
float Sample
Default sample types used where ever possible, changing this means the engine needs to be recompiled,...
Definition: vae.hpp:32
SmallHandle MixerHandle
Definition: vae.hpp:44
constexpr SourceHandle InvalidSourceHandle
Definition: vae.hpp:56
SmallHandle BankHandle
Definition: vae.hpp:40
SmallHandle ListenerHandle
Definition: vae.hpp:45
constexpr EventHandle InvalidEventHandle
Definition: vae.hpp:55
LargeHandle EmitterHandle
Definition: vae.hpp:43
Result
Return Types for most engine functions.
Definition: vae.hpp:73
@ VoiceStarvation
Could not play sound because of voice limit.
@ GenericFailure
:(
constexpr MixerHandle InvalidMixerHandle
Definition: vae.hpp:58
Settings for the engine defined at EnginePimpl::init.
Definition: vae.hpp:157
Size finishedVoiceQueueSize
Size of the voice queue for finished voices which need to trigger other events on_end when updating t...
Definition: vae.hpp:223
Size voices
Hard limit on concurrent voices, can't be 0 or lower than hrtfVoices.
Definition: vae.hpp:203
Size hrtfVoices
Amount of HRTF panned voices audible at any given time.
Definition: vae.hpp:209
Size virtualVoices
Hard limit on virtal voices.
Definition: vae.hpp:216
An Event is used to control most of the eingines behavior.
Definition: vae_event.hpp:14
EventHandle id
Own id.
Definition: vae_event.hpp:30
bool force_mixer
Prevents overriding the mixer from chained events or fireEvent.
Definition: vae_event.hpp:21
bool HRTF
Listener and event has to have hrtf set.
Definition: vae_event.hpp:23
Additional data needed for filtered voices.
Barebones voice.
Definition: vae_voice.hpp:17
SourceHandle source
If invalid, means voice is not playing.
Definition: vae_voice.hpp:28
bool chainedEvents
If this voice triggers events after it stopped playing.
Definition: vae_voice.hpp:19
EventHandle event
Which event triggered the voice to be played.
Definition: vae_voice.hpp:29
BankHandle bank
Which bank it belongs to.
Definition: vae_voice.hpp:27
bool HRTF
If the voice should be rendered using hrtfs.
Definition: vae_voice.hpp:22
Data to interpolate panning between blocks or do manual pan.
#define VAE_DEBUG_VOICES(msg,...)
Definition: vae_logger.hpp:90
#define VAE_PROFILER_SCOPE_NAMED(name)
Profiles a scope and names it.
#define VAE_PROFILER_SCOPE()
Profiles a scope.
#define VAE_PROFILER_PLOT(name, value)
Records a value.
Internal types used across VAE.