-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaudio_engine.cpp
396 lines (326 loc) · 10.6 KB
/
audio_engine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#include "audio_engine.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <SDL2/SDL.h>
namespace ste {
constexpr float PI = 3.14159265358979323846f;
constexpr float DISTANCE_FALLOFF = 1.0f;
constexpr float MAX_DISTANCE = 10.0f;
void AudioChannel::setPlaybackSpeed(float speed) {
m_targetSpeed = std::clamp(speed, 0.1f, 3.0f);
}
void AudioChannel::update(float deltaTime) {
if (!m_active || !m_currentFile)
return;
// Don't let the speed change too quickly
m_currentSpeed = std::lerp(m_currentSpeed, m_targetSpeed, deltaTime * 8.0f);
if (m_fadeTimeRemaining > 0.0f) {
m_fadeTimeRemaining = std::max(0.0f, m_fadeTimeRemaining - deltaTime);
float t = 1.0f - (m_fadeTimeRemaining / m_fadeDuration);
m_volume = std::lerp(m_volume, m_targetVolume, t);
if (m_fadeTimeRemaining == 0.0f) {
if (m_volume <= 0.0f) {
stop();
}
}
}
}
void AudioChannel::mix(float *buffer, size_t frames) {
if (!m_active || !m_currentFile || m_volume <= 0.0f)
return;
const float *sourceData = m_currentFile->data();
const size_t sourceSize = m_currentFile->size();
const size_t numChannels = m_currentFile->getChannels();
const float effectivePitch = m_pitch * m_currentSpeed;
float readPosition = static_cast<float>(m_position);
for (size_t frame = 0; frame < frames; ++frame) {
float sample = 0.0f;
if (numChannels == 1) {
sample = interpolateSample(readPosition);
} else {
float leftSample = interpolateSample(readPosition * 2.0f);
float rightSample = interpolateSample(readPosition * 2.0f + 1.0f);
sample = (leftSample + rightSample) * 0.5f;
}
float leftGain = m_volume;
float rightGain = m_volume;
applySpatialization(leftGain, rightGain);
buffer[frame * 2] += sample * leftGain;
buffer[frame * 2 + 1] += sample * rightGain;
readPosition += effectivePitch;
if (static_cast<size_t>(readPosition) >= sourceSize / numChannels) {
if (m_currentFile->isLooping()) {
readPosition = 0;
} else {
stop();
break;
}
}
}
m_position = static_cast<size_t>(readPosition);
}
void AudioChannel::play(AudioFile *file, float vol) {
if (!file)
return;
m_currentFile = file;
m_position = 0;
m_volume = vol;
m_targetVolume = vol;
m_fadeTimeRemaining = 0.0f;
m_active = true;
}
void AudioChannel::stop() {
m_active = false;
m_currentFile = nullptr;
m_position = 0;
m_currentSpeed = 1.0f;
m_targetSpeed = 1.0f;
}
void AudioChannel::setVolume(float vol) {
m_volume = std::clamp(vol, 0.0f, 1.0f);
m_targetVolume = m_volume;
m_fadeTimeRemaining = 0.0f;
}
void AudioChannel::setPitch(float newPitch) {
m_pitch = std::clamp(newPitch, 0.1f, 3.0f);
}
void AudioChannel::setPosition(float x, float y) {
m_positionX = x;
m_positionY = y;
}
void AudioChannel::fadeVolume(float target, float duration) {
target = std::clamp(target, 0.0f, 1.0f);
if (duration <= 0.0f) {
m_volume = target;
m_targetVolume = target;
m_fadeTimeRemaining = 0.0f;
} else {
m_targetVolume = target;
m_fadeDuration = duration;
m_fadeTimeRemaining = duration;
}
}
float AudioChannel::calculatePanLeft() const {
if (m_positionX == 0.0f)
return 1.0f;
return std::clamp(1.0f - m_positionX * 0.5f, 0.0f, 1.0f);
}
float AudioChannel::calculatePanRight() const {
if (m_positionX == 0.0f)
return 1.0f;
return std::clamp(1.0f + m_positionX * 0.5f, 0.0f, 1.0f);
}
void AudioChannel::applySpatialization(float &left, float &right) const {
constexpr float DISTANCE_FALLOFF = 1.0f;
// Calculate distance-based attenuation
float distance =
std::sqrt(m_positionX * m_positionX + m_positionY * m_positionY);
float attenuation = 1.0f;
if (distance > 0.0f) {
attenuation = std::min(1.0f, 1.0f / (1.0f + DISTANCE_FALLOFF * distance));
attenuation = std::pow(attenuation, 2.0f); // Quadratic falloff
}
// Apply panning
left *= calculatePanLeft() * attenuation;
right *= calculatePanRight() * attenuation;
}
float AudioChannel::interpolateSample(float position) const {
if (!m_currentFile) {
return 0.0f;
}
const float *data = m_currentFile->data();
const size_t size = m_currentFile->size();
// Get integer position and fractional part
size_t pos1 = static_cast<size_t>(std::floor(position));
float frac = position - static_cast<float>(pos1);
// Get the sample points
size_t pos0 = (pos1 == 0) ? pos1 : pos1 - 1; // Previous sample
size_t pos2 = (pos1 + 1 >= size) ? pos1 : pos1 + 1; // Next sample
size_t pos3 = (pos1 + 2 >= size) ? pos2 : pos1 + 2; // Next next sample
float p0 = data[pos0];
float p1 = data[pos1];
float p2 = data[pos2];
float p3 = data[pos3];
// Cubic Hermite coefficients
float t = frac;
float t2 = t * t;
float t3 = t2 * t;
// Hermite basis functions
float h0 = -0.5f * t3 + t2 - 0.5f * t;
float h1 = 1.5f * t3 - 2.5f * t2 + 1.0f;
float h2 = -1.5f * t3 + 2.0f * t2 + 0.5f * t;
float h3 = 0.5f * t3 - 0.5f * t2;
// Interpolate
return p0 * h0 + p1 * h1 + p2 * h2 + p3 * h3;
}
namespace {
// Audio callback function for the backend
void audioCallback(void *userData, float *buffer, size_t frames) {
auto *engine = static_cast<AudioEngine *>(userData);
engine->audioCallback(buffer, frames);
}
// Default audio settings
constexpr uint32_t DEFAULT_SAMPLE_RATE = 44100;
constexpr uint32_t DEFAULT_CHANNELS = 2;
constexpr uint32_t DEFAULT_BUFFER_SIZE = 1024;
} // namespace
AudioEngine::AudioEngine() {
// Initialize m_channels array
for (auto &channel : m_channels) {
channel.setVolume(1.0f);
channel.setPitch(1.0f);
channel.setPosition(0.0f, 0.0f);
}
}
AudioEngine::~AudioEngine() {
beginShutdown();
// Wait for ramp duration before actual cleanup
SDL_Delay(static_cast<Uint32>(SHUTDOWN_RAMP_DURATION * 1000));
stopAll();
}
void AudioEngine::playSound(AssetHandle<AudioFile> sound, float volume) {
if (!sound.isValid()) {
std::cerr << "Attempted to play invalid sound asset" << std::endl;
return;
}
int channel = findFreeChannel();
if (channel != -1) {
m_commandQueue.pushPlay(sound.operator->(), volume, channel);
}
}
void AudioEngine::playMusic(AssetHandle<AudioFile> music, bool loop) {
if (!music.isValid()) {
std::cerr << "Attempted to play invalid music asset" << std::endl;
return;
}
// Set looping state on the audio file
music->setLooping(loop);
// Stop any currently playing music
stopChannel(0);
// Queue the new music
m_commandQueue.pushPlay(music.operator->(), 1.0f,
0); // Channel 0 reserved for music
}
void AudioEngine::stopChannel(int channelId) {
if (channelId >= 0 && channelId < static_cast<int>(MAX_CHANNELS)) {
m_commandQueue.pushStop(channelId);
}
}
void AudioEngine::stopAll() {
for (int i = 0; i < static_cast<int>(MAX_CHANNELS); ++i) {
stopChannel(i);
}
m_commandQueue.clear();
}
void AudioEngine::setChannelVolume(int channelId, float m_volume) {
if (channelId >= 0 && channelId < static_cast<int>(MAX_CHANNELS)) {
m_commandQueue.pushVolume(channelId, std::clamp(m_volume, 0.0f, 1.0f));
}
}
void AudioEngine::setChannelPitch(int channelId, float m_pitch) {
if (channelId >= 0 && channelId < static_cast<int>(MAX_CHANNELS)) {
m_commandQueue.pushPitch(channelId, std::clamp(m_pitch, 0.1f, 3.0f));
}
}
void AudioEngine::setChannelPosition(int channelId, float x, float y) {
if (channelId >= 0 && channelId < static_cast<int>(MAX_CHANNELS)) {
m_commandQueue.pushPosition(channelId, x, y);
}
}
void AudioEngine::fadeChannel(int channelId, float m_targetVolume,
float duration) {
if (channelId >= 0 && channelId < static_cast<int>(MAX_CHANNELS)) {
m_commandQueue.pushFade(channelId, m_targetVolume,
std::max(0.0f, duration));
}
}
void AudioEngine::setMasterVolume(float m_volume) {
m_masterVolume = std::clamp(m_volume, 0.0f, 1.0f);
}
void AudioEngine::processCommands() {
m_commandQueue.processCommands([this](const AudioCommand &cmd) {
if (cmd.channelId < 0 || cmd.channelId >= static_cast<int>(MAX_CHANNELS)) {
return;
}
auto &channel = m_channels[cmd.channelId];
switch (cmd.type) {
case AudioCommand::Type::Play:
if (cmd.file) {
channel.play(cmd.file, cmd.value1);
}
break;
case AudioCommand::Type::Stop:
channel.stop();
break;
case AudioCommand::Type::SetVolume:
channel.setVolume(cmd.value1);
break;
case AudioCommand::Type::FadeIn:
case AudioCommand::Type::FadeOut:
channel.fadeVolume(cmd.value1, cmd.value2);
break;
case AudioCommand::Type::SetPitch:
channel.setPitch(cmd.value1);
break;
case AudioCommand::Type::SetPosition:
channel.setPosition(cmd.value1, cmd.value2);
break;
case AudioCommand::Type::SetLoop:
if (cmd.file) {
cmd.file->setLooping(cmd.flag);
}
break;
}
});
}
void AudioEngine::audioCallback(float *buffer, size_t frames) {
processCommands();
std::memset(buffer, 0, frames * DEFAULT_CHANNELS * sizeof(float));
size_t m_activeChannels = 0;
const float framesPerSecond = static_cast<float>(DEFAULT_SAMPLE_RATE);
float deltaTime = frames / framesPerSecond;
// Calculate shutdown ramp if needed
float shutdownRamp = 1.0f;
if (m_shutdownRequested) {
shutdownRamp =
std::max(0.0f, m_shutdownRampRemaining / SHUTDOWN_RAMP_DURATION);
m_shutdownRampRemaining -= deltaTime;
}
// Mix all m_active m_channels with speed control
for (auto &channel : m_channels) {
if (channel.isActive()) {
// Update channel with speed-adjusted time
channel.update(deltaTime * m_gameSpeed);
channel.setPlaybackSpeed(m_gameSpeed);
channel.mix(buffer, frames);
m_activeChannels++;
}
}
// Normalize and apply master m_volume
if (m_activeChannels > 0) {
float peakAmplitude = 0.0f;
for (size_t i = 0; i < frames * DEFAULT_CHANNELS; ++i) {
peakAmplitude = std::max(peakAmplitude, std::abs(buffer[i]));
}
float normalizationFactor =
(peakAmplitude > 1.0f) ? 1.0f / peakAmplitude : 1.0f;
float finalGain = normalizationFactor * m_masterVolume * shutdownRamp;
for (size_t i = 0; i < frames * DEFAULT_CHANNELS; ++i) {
buffer[i] = std::clamp(buffer[i] * finalGain, -1.0f, 1.0f);
}
}
}
int AudioEngine::findFreeChannel() {
// Skip channel 0 (reserved for music)
for (size_t i = 1; i < MAX_CHANNELS; ++i) {
if (!m_channels[i].isActive()) {
// Optionally force a stop to ensure clean state
m_channels[i].stop();
return static_cast<int>(i);
}
}
return -1; // No free m_channels available
}
}; // namespace ste