Cogs.Core
AudioSystem.cpp
1#include "AudioSystem.h"
2#include "AudioListenerComponent.h"
3#include "Context.h"
4
5#include "Systems/Core/TransformSystem.h"
6#include "Services/Time.h"
7
8#include "Foundation/Logging/Logger.h"
9
10#include <xaudio2.h>
11#include <x3daudio.h>
12#include <atlbase.h>
13
14
15namespace
16{
17 Cogs::Logging::Log logger = Cogs::Logging::getLogger("AudioSystem");
18}
19
20namespace Cogs
21{
22 namespace Core
23 {
25 {
27 {
28 if(initialized_com) CoUninitialize();
29 }
30 void initialize()
31 {
32 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
33 initialized_com = true;
34
35 HRESULT hr = XAudio2Create(&audioDevice, 0, XAUDIO2_DEFAULT_PROCESSOR);
36
37 if (FAILED(hr)) {
38 LOG_ERROR(logger, "Failed to initialize audio device.");
39 return;
40 }
41
42 //NOTE: Debug messages will appear in the VS Output window.
43 XAUDIO2_DEBUG_CONFIGURATION debugConfiguration = {};
44#ifdef _DEBUG
45 debugConfiguration.TraceMask = XAUDIO2_LOG_ERRORS;
46#endif
47 audioDevice->SetDebugConfiguration(&debugConfiguration);
48
49 hr = audioDevice->CreateMasteringVoice(&masteringVoice);
50
51 if (FAILED(hr)) {
52 LOG_ERROR(logger, "Could not create mastering voice.");
53 return;
54 }
55
56 DWORD channelMask = 0;
57 masteringVoice->GetChannelMask(&channelMask);
58
59 hr = X3DAudioInitialize(channelMask, X3DAUDIO_SPEED_OF_SOUND, x3DInstance);
60
61 if (FAILED(hr)) {
62 LOG_ERROR(logger, "Failed to initialize X3D Audio.");
63 return;
64 }
65
66 initialized = true;
67 }
68
69 CComPtr<IXAudio2> audioDevice;
70 IXAudio2MasteringVoice * masteringVoice;
71
72 X3DAUDIO_HANDLE x3DInstance;
73
74 bool initialized = false;
75 bool initialized_com = false;
76 };
77 }
78}
79
80Cogs::Core::AudioSystem::AudioSystem(Memory::Allocator * allocator, SizeType size) :
81 ComponentSystemWithDataPool(allocator, size),
82 subSystem(std::make_unique<AudioDevice>())
83{}
84
85Cogs::Core::AudioSystem::~AudioSystem()
86{
87}
88
90{
92
93 subSystem->initialize();
94}
95
96namespace
97{
98 X3DAUDIO_VECTOR toX3D(const glm::vec3 & v)
99 {
100 return *reinterpret_cast<const X3DAUDIO_VECTOR *>(&v);
101 }
102}
103
105{
106 if (!subSystem->initialized) return;
107
108 const double time = context->time->getAnimationTime();
109 float elapsed = static_cast<float>(time - prevTime);
110 prevTime = time;
111
112 X3DAUDIO_LISTENER listener = {};
113 X3DAUDIO_EMITTER emitter = {};
114
115 XAUDIO2_VOICE_DETAILS voiceDetails = {};
116 subSystem->masteringVoice->GetVoiceDetails(&voiceDetails);
117
118 float volume = 1.0f;
119
120 if(auto listenerComponent = listenerHandle.resolveComponent<AudioListenerComponent>(); listenerComponent) {
121 auto listenerTransformComponent = listenerComponent->getComponent<TransformComponent>();
122 auto & listenerTransform = context->transformSystem->getLocalToWorld(listenerTransformComponent);
123
124 // X3DAudio uses left-handed system.
125 auto front = glm::normalize(glm::vec3(listenerTransform * glm::vec4(0, 0, 1, 0)));
126 auto up = glm::normalize(glm::vec3(listenerTransform * glm::vec4(0, 1, 0, 0)));
127
128 auto listenerPosition = glm::vec3(listenerTransform * glm::vec4(0, 0, 0, 1));
129
130 listener.Position = toX3D(listenerPosition);
131 listener.OrientFront = toX3D(front);
132 listener.OrientTop = toX3D(up);
133
134 auto listenerDiff = (listenerPosition - listenerComponent->previousPosition) / elapsed;
135 listener.Velocity = toX3D(listenerDiff);
136 listenerComponent->previousPosition = listenerPosition;
137
138 volume = listenerComponent->volume;
139 }
140
141 for (auto & c : pool) {
142 auto & data = getData(&c);
143
144 if (data.state == AudioState::None && c.enabled && c.sound) {
145 auto hr = subSystem->audioDevice->CreateSourceVoice(&data.sourceVoice, &c.sound->format.Format);
146
147 if (FAILED(hr)) {
148 continue;
149 }
150
151 hr = subSystem->audioDevice->CreateSubmixVoice(&data.submixVoice, 2, voiceDetails.InputSampleRate, XAUDIO2_VOICE_USEFILTER, 0);
152
153 if (FAILED(hr)) {
154 continue;
155 }
156
157 // Send audio from source voice only through submix voice.
158 std::vector<XAUDIO2_SEND_DESCRIPTOR> descs = {
159 { 0, data.submixVoice }
160 };
161
162 XAUDIO2_VOICE_SENDS sends;
163 sends.SendCount = static_cast<uint32_t>(descs.size());
164 sends.pSends = descs.data();
165
166 hr = data.sourceVoice->SetOutputVoices(&sends);
167
168 if (FAILED(hr)) {
169 continue;
170 }
171
172 data.state = AudioState::Stopped;
173 }
174
175 if (c.enabled && data.state == AudioState::Stopped) {
176 data.state = AudioState::StartPlaying;
177 } else if (!c.enabled && data.state == AudioState::Playing) {
178 data.state = AudioState::StopPlaying;
179 }
180
181 if (data.state == AudioState::StopPlaying) {
182 data.sourceVoice->Stop();
183
184 if (!c.enabled) {
185 data.sourceVoice->DestroyVoice();
186 data.submixVoice->DestroyVoice();
187 data.state = AudioState::None;
188 } else {
189 data.state = AudioState::Stopped;
190 }
191 }
192
193 if (c.sound && data.state == AudioState::StartPlaying) {
194 XAUDIO2_BUFFER buffer = {};
195 buffer.pAudioData = (unsigned char *)c.sound->data.data();
196 buffer.AudioBytes = static_cast<uint32_t>(c.sound->data.size());
197 buffer.Flags = XAUDIO2_END_OF_STREAM;
198 buffer.LoopCount = c.loop ? XAUDIO2_LOOP_INFINITE : 0;
199
200 data.sourceVoice->Stop();
201 data.sourceVoice->FlushSourceBuffers();
202
203 auto hr = data.sourceVoice->SubmitSourceBuffer(&buffer);
204
205 if (FAILED(hr)) {
206 continue;
207 }
208
209 hr = data.sourceVoice->Start();
210
211 if (FAILED(hr)) {
212 continue;
213 }
214
215 data.state = AudioState::Playing;
216 }
217
218 if (data.state == AudioState::Playing) {
219 emitter.ChannelCount = 1;
220
221 std::vector<float> azimuths(emitter.ChannelCount);
222 emitter.pChannelAzimuths = azimuths.data();
223
224 auto transformComponent = c.getComponent<TransformComponent>();
225 auto & transform = context->transformSystem->getLocalToWorld(transformComponent);
226
227 auto emitterPosition = glm::vec3(transform * glm::vec4(0, 0, 0, 1));
228
229 auto emitterDiff = (emitterPosition - data.previousPosition) / elapsed;
230 emitter.Velocity = toX3D(emitterDiff);
231 data.previousPosition = emitterPosition;
232
233 auto front = glm::normalize(glm::vec3(transform * glm::vec4(0, 0, 1, 0)));
234 auto up = glm::normalize(glm::vec3(transform * glm::vec4(0, 1, 0, 0)));
235
236 emitter.Position = toX3D(emitterPosition);
237 emitter.OrientFront = toX3D(front);
238 emitter.OrientTop = toX3D(up);
239
240 emitter.CurveDistanceScaler = c.radius;
241 emitter.DopplerScaler = c.dopplerScale;
242
243 std::vector<float> coefficients(2 * voiceDetails.InputChannels);
244
245 X3DAUDIO_DSP_SETTINGS dspSettings = {};
246 dspSettings.SrcChannelCount = emitter.ChannelCount;
247 dspSettings.DstChannelCount = voiceDetails.InputChannels;
248 dspSettings.pMatrixCoefficients = coefficients.data();
249
250 X3DAudioCalculate(subSystem->x3DInstance,
251 &listener,
252 &emitter,
253 X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_REVERB | X3DAUDIO_CALCULATE_LPF_DIRECT,
254 &dspSettings);
255
256 auto hr = data.sourceVoice->SetOutputMatrix(data.submixVoice,
257 2,
258 voiceDetails.InputChannels,
259 dspSettings.pMatrixCoefficients);
260
261 if (FAILED(hr)) {
262 continue;
263 }
264
265 hr = data.sourceVoice->SetFrequencyRatio(dspSettings.DopplerFactor * c.frequencyModulation);
266
267 if (FAILED(hr)) {
268 continue;
269 }
270
271 XAUDIO2_FILTER_PARAMETERS FilterParameters = {
272 LowPassFilter,
273 2.0f * sinf(X3DAUDIO_PI / 6.0f * dspSettings.LPFDirectCoefficient),
274 1.0f
275 };
276
277 data.submixVoice->SetFilterParameters(&FilterParameters);
278
279 data.sourceVoice->SetVolume(volume * c.volume);
280 }
281 }
282}
283
285{
286
287}
288
290{
291 auto component = handle.resolveComponent<AudioComponent>();
292
293 auto & data = getData(component);
294
295 if (data.state == AudioState::Playing || data.state == AudioState::StopPlaying) {
296 data.sourceVoice->Stop();
297 data.sourceVoice->DestroyVoice();
298 data.submixVoice->DestroyVoice();
299 }
300
302}
303
304void Cogs::Core::AudioSystem::setAudioListener(ComponentHandle handle)
305{
306 listenerHandle = handle;
307}
void initialize(Context *context) override
Initialize the system.
Definition: AudioSystem.cpp:89
void destroyComponent(ComponentHandle handle) override
void cleanup(Context *context) override
Provided for custom cleanup logic in derived systems.
virtual void initialize(Context *context)
Initialize the system.
virtual void destroyComponent(ComponentHandle)
Destroy the component held by the given handle.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Time > time
Time service instance.
Definition: Context.h:198
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:139
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
STL namespace.
Handle to a Component instance.
Definition: Component.h:67
ComponentType * resolveComponent() const
Definition: Component.h:90