Skip to content

Commit b0ef52b

Browse files
committed
Rework mixer to support reverb gain and spatial blend;
1 parent c442c0b commit b0ef52b

File tree

1 file changed

+110
-63
lines changed

1 file changed

+110
-63
lines changed

src/modules/audio/audio.c

Lines changed: 110 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ static void phonon_destroy(void);
140140
static void phonon_update(float dt);
141141
static bool phonon_set_hrtf(Blob* blob);
142142
static void phonon_mix_begin(void);
143-
static bool phonon_mix_source(Source* source, float* src, float* dst, float* tmp);
144-
static void phonon_mix_tail(float* output, float* temp);
143+
static bool phonon_mix_source(Source* source, float* src, float* dst);
144+
static void phonon_mix_reverb(float* dst);
145145
static bool phonon_source_init(Source* source);
146146
static void phonon_source_destroy(Source* source);
147147
static void phonon_source_add(Source* source);
@@ -168,12 +168,14 @@ static void onPlayback(ma_device* device, void* out, const void* in, uint32_t co
168168
uint32_t play = atomic_exchange(&source->playRequest, ~0u);
169169

170170
if (play != ~0u) {
171+
#ifdef LOVR_USE_PHONON
171172
if (!source->playing && play == 1) {
172173
iplDirectEffectReset(source->directEffect);
173174
iplPanningEffectReset(source->panningEffect);
174175
iplBinauralEffectReset(source->binauralEffect);
175176
iplReflectionEffectReset(source->reflectionEffect);
176177
}
178+
#endif
177179

178180
source->playing = !!play;
179181
}
@@ -184,23 +186,21 @@ static void onPlayback(ma_device* device, void* out, const void* in, uint32_t co
184186
bool pitchChanged = atomic_exchange(&source->pitchChanged, false);
185187
if (pitchChanged) ma_data_converter_set_rate_ratio(source->converter, source->pitchRatio);
186188

187-
bool hasTail = false;
189+
uint32_t channels = lovrSoundGetChannelCount(source->sound);
188190

189191
if (source->playing) {
190192
// Read and convert raw frames until there's BUFFER_SIZE converted frames
191193
// - No converter: just read frames into raw
192194
// - Converter: keep reading as many frames as possible/needed into raw and convert into tmp.
193195
// - If EOF is reached, rewind and continue for looping sources, otherwise pad end with zero.
194196
float* cursor = source->converter ? tmp : raw; // Edge of processed frames
195-
uint32_t channelsOut = source->spatial ? 1 : 2; // If spatializer isn't converting to stereo, converter must do it
196197
uint32_t framesRemaining = BUFFER_SIZE;
197198

198199
while (framesRemaining > 0) {
199200
uint32_t framesRead;
200201

201202
if (source->converter) {
202-
uint32_t channelsIn = lovrSoundGetChannelCount(source->sound);
203-
uint32_t capacity = sizeof(raw) / (channelsIn * sizeof(float));
203+
uint32_t capacity = sizeof(raw) / (channels * sizeof(float));
204204
ma_uint64 chunk;
205205
ma_data_converter_get_required_input_frame_count(source->converter, framesRemaining, &chunk);
206206
framesRead = lovrSoundRead(source->sound, source->offset, MIN(chunk, capacity), raw);
@@ -215,7 +215,7 @@ static void onPlayback(ma_device* device, void* out, const void* in, uint32_t co
215215
} else {
216216
source->offset = 0;
217217
source->playing = false;
218-
memset(cursor, 0, framesRemaining * channelsOut * sizeof(float));
218+
memset(cursor, 0, framesRemaining * channels * sizeof(float));
219219
break;
220220
}
221221
} else {
@@ -226,35 +226,41 @@ static void onPlayback(ma_device* device, void* out, const void* in, uint32_t co
226226
ma_uint64 framesIn = framesRead;
227227
ma_uint64 framesOut = framesRemaining;
228228
ma_data_converter_process_pcm_frames(source->converter, raw, &framesIn, cursor, &framesOut);
229-
cursor += framesOut * channelsOut;
229+
cursor += framesOut * channels;
230230
framesRemaining -= framesOut;
231231
} else {
232-
cursor += framesRead * channelsOut;
232+
cursor += framesRead * channels;
233233
framesRemaining -= framesRead;
234234
}
235235
}
236236

237237
buf = source->converter ? tmp : raw;
238238
}
239239

240-
bool tail = false;
240+
// Scale
241+
for (uint32_t i = 0; i < channels * BUFFER_SIZE; i++) {
242+
buf[i] *= source->volume;
243+
}
241244

245+
// Spatialize
242246
if (source->spatial) {
243-
tail = phonon_mix_source(source, buf, mix, buf == raw ? tmp : raw);
247+
source->hasTail = phonon_mix_source(source, buf, mix);
248+
buf = mix;
249+
} else if (channels == 1) {
250+
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
251+
mix[i * 2 + 0] = buf[i];
252+
mix[i * 2 + 1] = buf[i];
253+
}
244254
buf = mix;
245255
}
246256

247257
// Mix
248-
float volume = source->volume;
249258
for (uint32_t i = 0; i < 2 * BUFFER_SIZE; i++) {
250-
dst[i] += buf[i] * volume;
259+
dst[i] += buf[i];
251260
}
252-
253-
// Once we set this to false, the source could get destroyed (if it's not playing)
254-
source->hasTail = tail;
255261
}
256262

257-
phonon_mix_tail(dst, tmp);
263+
phonon_mix_reverb(dst);
258264

259265
if (state.sinks[AUDIO_PLAYBACK]) {
260266
uint64_t capacity = sizeof(tmp) / lovrSoundGetChannelCount(state.sinks[AUDIO_PLAYBACK]) / sizeof(float);
@@ -549,12 +555,12 @@ Source* lovrSourceCreate(Sound* sound, bool pitchable, bool spatial) {
549555
config.formatIn = miniaudioFormats[lovrSoundGetFormat(sound)];
550556
config.formatOut = miniaudioFormats[OUTPUT_FORMAT];
551557
config.channelsIn = lovrSoundGetChannelCount(sound);
552-
config.channelsOut = spatial ? 1 : 2;
558+
config.channelsOut = lovrSoundGetChannelCount(sound);
553559
config.sampleRateIn = lovrSoundGetSampleRate(sound);
554560
config.sampleRateOut = state.config.sampleRate;
555561
config.allowDynamicSampleRate = pitchable;
556562

557-
if (pitchable || config.formatIn != config.formatOut || config.channelsIn != config.channelsOut || config.sampleRateIn != config.sampleRateOut) {
563+
if (pitchable || config.formatIn != config.formatOut || config.sampleRateIn != config.sampleRateOut) {
558564
ma_data_converter* converter = lovrMalloc(sizeof(ma_data_converter));
559565
ma_result status = ma_data_converter_init(&config, NULL, converter);
560566

@@ -1199,91 +1205,132 @@ static void phonon_mix_begin(void) {
11991205
}
12001206
}
12011207

1202-
static bool phonon_mix_source(Source* source, float* _src, float* dst, float* _tmp) {
1203-
IPLAudioBuffer src = { .numChannels = 1, .numSamples = BUFFER_SIZE, .data = &_src };
1204-
IPLAudioBuffer tmp1 = { .numChannels = 1, .numSamples = BUFFER_SIZE, .data = &_tmp };
1205-
IPLAudioBuffer tmp2 = { .numChannels = 2, .numSamples = BUFFER_SIZE, .data = (float*[2]) { _tmp, _tmp + BUFFER_SIZE } };
1206-
1208+
static bool phonon_mix_source(Source* source, float* src, float* dst) {
1209+
float left[BUFFER_SIZE], right[BUFFER_SIZE];
1210+
uint32_t channels = lovrSoundGetChannelCount(source->sound);
12071211
uint32_t index = !state.backbuffer;
12081212
bool tail = false;
12091213

12101214
// Tail
12111215
if (!source->playing) {
1216+
IPLAudioBuffer buffer = { 2, BUFFER_SIZE, (float*[2]) { left, right } };
1217+
12121218
if (iplBinauralEffectGetTailSize(source->binauralEffect) > 0) {
1213-
tail |= !iplBinauralEffectGetTail(source->binauralEffect, &tmp2);
1214-
iplAudioBufferInterleave(state.phonon, &tmp2, dst);
1219+
tail |= !iplBinauralEffectGetTail(source->binauralEffect, &buffer);
1220+
iplAudioBufferInterleave(state.phonon, &buffer, dst);
12151221
} else {
12161222
memset(dst, 0, 2 * BUFFER_SIZE * sizeof(float));
12171223
}
12181224

12191225
if (iplReflectionEffectGetTailSize(source->reflectionEffect) > 0) {
12201226
if (state.config.reverb.type == REVERB_CONVOLUTION) {
1221-
tail |= !iplReflectionEffectGetTail(source->reflectionEffect, &tmp1, state.reflectionMixer);
1227+
tail |= !iplReflectionEffectGetTail(source->reflectionEffect, &buffer, state.reflectionMixer);
12221228
} else {
1223-
tail |= !iplReflectionEffectGetTail(source->reflectionEffect, &tmp1, NULL);
1224-
iplAudioBufferMix(state.phonon, &tmp1, &state.reflectionBuffer);
1229+
buffer.numChannels = 1;
1230+
tail |= !iplReflectionEffectGetTail(source->reflectionEffect, &buffer, NULL);
1231+
iplAudioBufferMix(state.phonon, &buffer, &state.reflectionBuffer);
12251232
}
12261233
}
12271234

12281235
return tail;
12291236
}
12301237

1238+
// Prepare input (since we always copy src, it can be reused as a temporary buffer after this)
1239+
IPLAudioBuffer input = { channels, BUFFER_SIZE, (float*[2]) { left, right } };
1240+
1241+
if (channels == 1) {
1242+
input.data[1] = left; // Alias both channels, useful for spatialization
1243+
memcpy(left, src, BUFFER_SIZE * sizeof(float));
1244+
} else {
1245+
iplAudioBufferDeinterleave(state.phonon, src, &input);
1246+
}
1247+
12311248
// Reverb
12321249
if (source->reverb > 0.f && state.enabledMeshCount > 0) {
1250+
if (channels == 2) {
1251+
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
1252+
src[i] = (left[i] + right[i]) * .5f * source->reverb;
1253+
}
1254+
} else if (source->reverb != 1.f) {
1255+
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
1256+
src[i] *= source->reverb;
1257+
}
1258+
}
1259+
1260+
IPLAudioBuffer reverbInput = { 1, BUFFER_SIZE, &src };
1261+
12331262
if (source->reverbMode == REVERB_SOURCE) {
12341263
IPLSimulationOutputs outputs = { 0 };
12351264
iplSourceGetOutputs(source->handle, IPL_SIMULATIONFLAGS_REFLECTIONS, &outputs);
12361265

12371266
if (state.config.reverb.type == REVERB_CONVOLUTION) {
1238-
tail |= !iplReflectionEffectApply(source->reflectionEffect, &outputs.reflections, &src, &tmp1, state.reflectionMixer);
1267+
tail |= !iplReflectionEffectApply(source->reflectionEffect, &outputs.reflections, &reverbInput, &reverbInput, state.reflectionMixer);
12391268
} else {
1240-
tail |= !iplReflectionEffectApply(source->reflectionEffect, &outputs.reflections, &src, &tmp1, NULL);
1241-
iplAudioBufferMix(state.phonon, &tmp1, &state.reflectionBuffer);
1269+
IPLAudioBuffer out = { 1, BUFFER_SIZE, (float*[1]) { dst } };
1270+
tail |= !iplReflectionEffectApply(source->reflectionEffect, &outputs.reflections, &reverbInput, &out, NULL);
1271+
iplAudioBufferMix(state.phonon, &out, &state.reflectionBuffer);
12421272
}
12431273
} else if (state.reverb > 0.f) {
1244-
iplAudioBufferMix(state.phonon, &src, &state.listenerReverbInput);
1274+
iplAudioBufferMix(state.phonon, &reverbInput, &state.listenerReverbInput);
12451275
}
12461276
}
12471277

12481278
// Direct effects, applied in-place
1249-
IPLDirectEffectParams* directParams = &source->outputs[index].direct;
1250-
if (directParams->flags) {
1251-
tail |= !iplDirectEffectApply(source->directEffect, directParams, &src, &src);
1279+
if (source->outputs[index].direct.flags) {
1280+
tail |= !iplDirectEffectApply(source->directEffect, &source->outputs[index].direct, &input, &input);
12521281
}
12531282

12541283
// Spatialization (either binaural, panning, or upmix)
12551284
if (source->spatialization > 0.f) {
1285+
// Can reuse src as temporary buffer for spatialization output
1286+
IPLAudioBuffer spatialized = { 2, BUFFER_SIZE, (float*[2]) { src, src + BUFFER_SIZE } };
1287+
12561288
if (state.hrtf[0]) {
12571289
IPLBinauralEffectParams params = {
12581290
.direction = source->relativeDirection[index],
12591291
.interpolation = IPL_HRTFINTERPOLATION_BILINEAR,
12601292
.spatialBlend = source->spatialization,
12611293
.hrtf = state.hrtf[0]
12621294
};
1263-
1264-
tail |= !iplBinauralEffectApply(source->binauralEffect, &params, &src, &tmp2);
1295+
input.numChannels = 2; // For mono input, left/right channels are aliased to same buffer
1296+
tail |= !iplBinauralEffectApply(source->binauralEffect, &params, &input, &spatialized);
12651297
} else {
1266-
IPLPanningEffectParams params = {
1267-
.direction = source->relativeDirection[index]
1268-
};
1298+
IPLAudioBuffer mono = { 1, BUFFER_SIZE, .data = channels == 2 ? &dst : input.data };
1299+
1300+
// Stereo input uses dst as temporary buffer to hold downmixed audio, for panning effect
1301+
if (channels == 2) {
1302+
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
1303+
dst[i] = (left[i] + right[i]) * .5f;
1304+
}
1305+
}
12691306

1270-
iplPanningEffectApply(source->panningEffect, &params, &src, &tmp2);
1307+
IPLPanningEffectParams params = { .direction = source->relativeDirection[index] };
1308+
iplPanningEffectApply(source->panningEffect, &params, &mono, &spatialized);
1309+
1310+
if (source->spatialization < 1.f) {
1311+
float s = source->spatialization, t = 1.f - s;
1312+
for (uint32_t c = 0; c < 2; c++) {
1313+
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
1314+
spatialized.data[c][i] = spatialized.data[c][i] * s + input.data[c][i] * t;
1315+
}
1316+
}
1317+
}
12711318
}
12721319

1273-
iplAudioBufferInterleave(state.phonon, &tmp2, dst);
1320+
iplAudioBufferInterleave(state.phonon, &spatialized, dst);
12741321
} else {
1275-
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
1276-
dst[2 * i + 0] = _src[i];
1277-
dst[2 * i + 1] = _src[i];
1278-
}
1322+
input.numChannels = 2;
1323+
iplAudioBufferInterleave(state.phonon, &input, dst);
12791324
}
12801325

12811326
return tail;
12821327
}
12831328

1284-
static void phonon_mix_tail(float* dst, float* _tmp) {
1285-
IPLAudioBuffer tmp1 = { .numChannels = 1, .numSamples = BUFFER_SIZE, .data = &_tmp };
1286-
IPLAudioBuffer tmp2 = { .numChannels = 2, .numSamples = BUFFER_SIZE, .data = (float*[2]) { _tmp, _tmp + BUFFER_SIZE } };
1329+
static void phonon_mix_reverb(float* dst) {
1330+
float left[BUFFER_SIZE], right[BUFFER_SIZE];
1331+
1332+
IPLAudioBuffer mono = { 1, BUFFER_SIZE, (float*[1]) { left } };
1333+
IPLAudioBuffer stereo = { 2, BUFFER_SIZE, (float*[2]) { left, right } };
12871334

12881335
bool anyReverb = state.sourceReverbMask || state.listenerReverbMask;
12891336

@@ -1293,17 +1340,17 @@ static void phonon_mix_tail(float* dst, float* _tmp) {
12931340
iplSourceGetOutputs(state.listener, IPL_SIMULATIONFLAGS_REFLECTIONS, &outputs);
12941341

12951342
if (state.config.reverb.type == REVERB_CONVOLUTION) {
1296-
iplReflectionEffectApply(state.reflectionEffect, &outputs.reflections, &state.listenerReverbInput, &tmp1, state.reflectionMixer);
1343+
iplReflectionEffectApply(state.reflectionEffect, &outputs.reflections, &state.listenerReverbInput, &mono, state.reflectionMixer);
12971344
} else {
1298-
iplReflectionEffectApply(state.reflectionEffect, &outputs.reflections, &state.listenerReverbInput, &tmp1, NULL);
1299-
iplAudioBufferMix(state.phonon, &tmp1, &state.reflectionBuffer);
1345+
iplReflectionEffectApply(state.reflectionEffect, &outputs.reflections, &state.listenerReverbInput, &mono, NULL);
1346+
iplAudioBufferMix(state.phonon, &mono, &state.reflectionBuffer);
13001347
}
13011348
} else if (iplReflectionEffectGetTailSize(state.reflectionEffect) > 0) {
13021349
if (state.config.reverb.type == REVERB_CONVOLUTION) {
1303-
iplReflectionEffectGetTail(state.reflectionEffect, &tmp1, state.reflectionMixer);
1350+
iplReflectionEffectGetTail(state.reflectionEffect, &mono, state.reflectionMixer);
13041351
} else {
1305-
iplReflectionEffectGetTail(state.reflectionEffect, &tmp1, NULL);
1306-
iplAudioBufferMix(state.phonon, &tmp1, &state.reflectionBuffer);
1352+
iplReflectionEffectGetTail(state.reflectionEffect, &mono, NULL);
1353+
iplAudioBufferMix(state.phonon, &mono, &state.reflectionBuffer);
13071354
}
13081355

13091356
anyReverb = true;
@@ -1325,16 +1372,16 @@ static void phonon_mix_tail(float* dst, float* _tmp) {
13251372
.binaural = !!state.hrtf[0]
13261373
};
13271374

1328-
iplAmbisonicsDecodeEffectApply(state.ambisonicsDecodeEffect, &ambisonicsDecodeParams, &state.reflectionBuffer, &tmp2);
1375+
iplAmbisonicsDecodeEffectApply(state.ambisonicsDecodeEffect, &ambisonicsDecodeParams, &state.reflectionBuffer, &stereo);
13291376
} else if (iplAmbisonicsDecodeEffectGetTailSize(state.ambisonicsDecodeEffect) > 0) {
1330-
iplAmbisonicsDecodeEffectGetTail(state.ambisonicsDecodeEffect, &tmp2);
1377+
iplAmbisonicsDecodeEffectGetTail(state.ambisonicsDecodeEffect, &stereo);
13311378
} else {
13321379
return;
13331380
}
13341381

13351382
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
1336-
dst[2 * i + 0] += tmp2.data[0][i];
1337-
dst[2 * i + 1] += tmp2.data[1][i];
1383+
dst[2 * i + 0] += stereo.data[0][i];
1384+
dst[2 * i + 1] += stereo.data[1][i];
13381385
}
13391386
} else if (anyReverb) {
13401387
for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
@@ -1364,7 +1411,7 @@ static bool phonon_source_init(Source* source) {
13641411
vec3_set(source->inputs.reverbScale, 1.f, 1.f, 1.f);
13651412

13661413
IPLDirectEffectSettings directEffectSettings = {
1367-
.numChannels = 1
1414+
.numChannels = lovrSoundGetChannelCount(source->sound)
13681415
};
13691416

13701417
if (iplDirectEffectCreate(state.phonon, &state.audioSettings, &directEffectSettings, &source->directEffect)) {
@@ -1569,8 +1616,8 @@ static void phonon_destroy(void) {}
15691616
static void phonon_update(float dt) {}
15701617
static bool phonon_set_hrtf(Blob* blob) { return true; }
15711618
static void phonon_mix_begin(void) {}
1572-
static bool phonon_mix_source(Source* source, float* src, float* dst, float* tmp) {}
1573-
static void phonon_mix_tail(float* output, float* temp) {}
1619+
static bool phonon_mix_source(Source* source, float* src, float* dst) { return false; }
1620+
static void phonon_mix_reverb(float* dst) {}
15741621
static bool phonon_source_init(Source* source) { return true; }
15751622
static void phonon_source_destroy(Source* source) {}
15761623
static void phonon_source_add(Source* source) {}

0 commit comments

Comments
 (0)