VAE - Virtual Audio Engine 1
Small Data Driven Audio Engine
vae_processor.hpp
Go to the documentation of this file.
1#ifndef _VAE_PROCESSOR
2#define _VAE_PROCESSOR
3
4#include "../vae_types.hpp"
5#include "../vae_util.hpp"
6#include "../pod/vae_bank.hpp"
7#include "../voices/vae_voice.hpp"
8#include "../voices/vae_voice_filter.hpp"
9#include "../voices/vae_voice_pan.hpp"
10#include "../vae_voice_manager.hpp"
11#include <cmath>
12#include <limits>
13
14namespace vae { namespace core {
15 /**
16 * @brief Non spatial voice processor
17 */
18 class Processor {
19 /**
20 * @brief Temporary filtered/looped signal TODO this will not work with parallel bank processing
21 */
23 public:
26 return Result::Success;
27 }
28
29 /**
30 * @brief Process a single bank
31 *
32 * @param manager
33 * @param banks
34 * @param frames
35 * @param sampleRate
36 * @return Voices renderd
37 */
39 VoiceManger& manager, Bank& bank,
40 SampleIndex frames, Size sampleRate
41 ) {
42 Size actuallyRendered = 0;
43 VAE_PROFILER_SCOPE_NAMED("Default Processor")
44 manager.forEachVoice([&](Voice& v, Size index) {
45 if (v.bank != bank.id) { return true; }
46 if (v.spatialized) { return true; }
47 VAE_PROFILER_SCOPE_NAMED("Default Voice")
48
49 auto& source = bank.sources[v.source];
50 auto& signal = source.signal;
51
52 const SampleIndex signalLength = signal.size();
53
54 if (signalLength == 0) { return false; }
55 if (signal.sampleRate != sampleRate) {
56 // VAE_DEBUG("Spatial Voice samplerate mismatch. Enabled filter.")
57 v.filtered = true; // implicitly filter to resample
58 }
59
60 v.time = v.time % signalLength; // Keep signal in bounds before starting
61
62 const auto signalChannels = signal.channels();
63 auto& mixer = bank.mixers[v.mixer];
64 auto& target = mixer.buffer;
65 const auto targetChannels = target.channels();
66 const auto gain = v.gain * source.gain;
67
68 // TODO skip inaudible sounds
69 actuallyRendered++;
70 v.audible = true;
71 auto& pan = manager.getVoicePan(index);
72 target.setValidSize(frames); // mark mixer as active
73
74 if (!v.filtered) {
75 VAE_PROFILER_SCOPE_NAMED("Render Voice Basic")
76 // Basic rendering to all output channels w/o any effects
77 v.started = true;
78 const SampleIndex needed = v.loop ? frames : std::min(frames, SampleIndex(signalLength - v.time));
79
80 if (v.loop) {
81 for (int c = 0; c < targetChannels; c++) {
82 const int channel = c % signalChannels;
83 for (SampleIndex s = 0; s < needed; s++) {
84 target[c][s] +=
85 signal[channel][((v.time + s) % signalLength)] * gain * pan.volumes[c];
86 }
87 }
88 } else {
89 // Having these seperate results in like a 3x speedup
90 // TODO check this in a more diffictult to branchpredict scenario
91 for (int c = 0; c < targetChannels; c++) {
92 for (SampleIndex s = 0; s < needed; s++) {
93 const int channel = c % signalChannels;
94 target[c][s] +=
95 signal[channel][v.time + s] * gain * pan.volumes[c];
96 }
97 }
98 }
99
100 v.time = v.time + frames; // progress voice
101 return needed == frames; // Finished if there are no samples left in source
102 }
103
104 // Filtered voice processing
105 {
106 VAE_PROFILER_SCOPE_NAMED("Render filtered Voice")
107 auto& fd = manager.getVoiceFilter(index);
108
109 if (!v.started) {
110 // Initialize filter variables when first playing the voice
111 for (int c = 0; c < StaticConfig::MaxChannels; c++) {
112 fd.highpassScratch[c] = 0;
113 fd.lowpassScratch[c] = signal[c % signalChannels][v.time];
114 }
115 v.started = true;
116 }
117
118 // fractional time, we need the value after the loop, so it's defined outside
119 Real position;
120
121 // Playback speed taking samplerate into account
122 const Real speed = fd.speed * (Sample(signal.sampleRate) / Sample(sampleRate));
123 const SampleIndex needed = v.loop ? frames : std::min(
124 frames, SampleIndex(std::floor((signalLength - v.time) / speed - fd.timeFract))
125 );
126
127 for (int c = 0; c < target.channels(); c++) {
128 for (SampleIndex s = 0; s < needed; s++) {
129 const int channel = c % signal.channels();
130 // Linear interpolation between two samples
131 position = v.time + (s * speed) + fd.timeFract;
132 const Real lastPosition = std::floor(position);
133 const Size lastIndex = (Size) lastPosition;
134 const Size nextIndex = (Size) lastPosition + 1;
135
136 Real mix = position - lastPosition;
137 // mix = 0.5 * (1.0 - cos((mix) * 3.1416)); // cosine interpolation, introduces new harmonics somehow
138 const Sample last = signal[channel][lastIndex % signalLength] * gain;
139 const Sample next = signal[channel][nextIndex % signalLength] * gain;
140 // linear resampling, sounds alright enough
141 const Sample in = last + mix * (next - last);
142
143 // * super simple lowpass and highpass filter
144 // just lerps with a previous value
145 const Sample lf = fd.lowpass;
146 const Sample lpd = in + lf * (fd.lowpassScratch[c] - in);
147 fd.lowpassScratch[c] = lpd;
148
149 const Sample hf = fd.highpass;
150 const Sample hps = fd.highpassScratch[c];
151 const Sample hpd = hps + hf * (in - hps);
152 fd.highpassScratch[c] = hpd;
153
154 target[c][s] += (lpd - hpd) * pan.volumes[c];
155 }
156 }
157 position += speed; // step to next sample
158 v.time = (SampleIndex) std::floor(position); // split the signal in normal sample position
159 fd.timeFract = position - v.time; // and fractional time for the next block
160 return needed == frames; // we might have reached the end; // is only true when exceeding signalLength and not looping
161 }
162 });
163 return actuallyRendered;
164 }
165 };
166
167} } // vae::core
168
169#endif // _VAE_PROCESSOR
bool resize(const Size length, uchar channels)
! Will not keep the contents! Resizes the buffer to the desired length and channel count.
Non spatial voice processor.
Size mix(VoiceManger &manager, Bank &bank, SampleIndex frames, Size sampleRate)
Process a single bank.
ScratchBuffer mScratchBuffer
Temporary filtered/looped signal TODO this will not work with parallel bank processing.
There is only one voice pool and VAE and it's managed here.
VoicePan & getVoicePan(Size index)
void forEachVoice(const Func &&func)
Callback provided to iterate voices, needs to return a bool to indicate when a voice needs to be stop...
T min(const T &v1, const T &v2)
Definition: TMath.hpp:16
constexpr Size MaxBlock
Maximum block size.
Definition: vae.hpp:276
constexpr unsigned char MaxChannels
Maximum channel count used to pre allocate buffers.
Definition: vae.hpp:268
AudioBuffer::Size SampleIndex
Definition: vae_types.hpp:87
float Real
Definition: vae_types.hpp:48
Contains Typedefinitions and basic structures use by the public API and internally.
Definition: vae.hpp:31
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
Result
Return Types for most engine functions.
Definition: vae.hpp:73
Bank object containing Sources, Mixers and Events Can be loaded and unloaded at runtime.
Definition: vae_bank.hpp:14
HeapBuffer< Mixer > mixers
Audio Mixers which can have effects ! is presorted !
Definition: vae_bank.hpp:16
BankHandle id
Definition: vae_bank.hpp:18
HeapBuffer< Source > sources
Audio sources defined.
Definition: vae_bank.hpp:15
Barebones voice.
Definition: vae_voice.hpp:17
SourceHandle source
If invalid, means voice is not playing.
Definition: vae_voice.hpp:28
bool spatialized
If the voice has spatialization data.
Definition: vae_voice.hpp:18
Sample gain
Volume of the voice.
Definition: vae_voice.hpp:33
bool audible
Whether the voice was heard by any listener.
Definition: vae_voice.hpp:21
BankHandle bank
Which bank it belongs to.
Definition: vae_voice.hpp:27
bool filtered
This will enable high/lowpass filters and variable speed playback. Gets turned on when signal does no...
Definition: vae_voice.hpp:24
MixerHandle mixer
Where the voice should mix to.
Definition: vae_voice.hpp:31
SampleIndex time
Current time in samples.
Definition: vae_voice.hpp:34
#define VAE_PROFILER_SCOPE_NAMED(name)
Profiles a scope and names it.