From 7d3111d21315d10c19e05711070cfb18b95fb95c Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 27 Jul 2026 09:39:40 -0700 Subject: [PATCH] [openal] Fix AL_SAMPLE_OFFSET and AL_BYTE_OFFSET with buffer 0 When `AL_SAMPLE_OFFSET` or `AL_BYTE_OFFSET` is set on a source, the library looks up the format of the queued audio data to convert the offset into seconds for `sourceSeek`. Previously, this iteration used `for (var bufId in src.bufQueue)` and checked `if (bufId)`. Because `for..in` iterates over array index strings (`"0"`, `"1"`, etc.), `bufId` received `"0"` on the first iteration. In JavaScript, non-empty strings are truthy, so `if (bufId)` evaluated `if ("0")`, which is always true. As a result, the loop never skipped index 0. If `src.bufQueue[0]` happened to be the default placeholder dummy zero buffer (which has `frequency: 0`), it would read `frequency = 0` and cause a division by zero (`value /= 0`), failing range validation with `AL_INVALID_VALUE`. This change replaces both loops in `src/lib/libopenal.js` with `for (var buf of src.bufQueue)` and checks `if (buf.id !== 0)`. A new test case is added to `test/openal/test_openal_error.c` to verify this behavior. --- src/lib/libopenal.js | 11 +++++------ test/openal/test_openal_error.c | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/lib/libopenal.js b/src/lib/libopenal.js index 3070aa6b53fa9..1961aeebcf447 100644 --- a/src/lib/libopenal.js +++ b/src/lib/libopenal.js @@ -1453,9 +1453,9 @@ var LibraryOpenAL = { var srcLen = AL.sourceDuration(src); if (srcLen > 0.0) { var frequency; - for (var bufId in src.bufQueue) { - if (bufId) { - frequency = src.bufQueue[bufId].frequency; + for (var buf of src.bufQueue) { + if (buf.id !== 0) { + frequency = buf.frequency; break; } } @@ -1475,9 +1475,8 @@ var LibraryOpenAL = { var srcLen = AL.sourceDuration(src); if (srcLen > 0.0) { var bytesPerSec; - for (var bufId in src.bufQueue) { - if (bufId) { - var buf = src.bufQueue[bufId]; + for (var buf of src.bufQueue) { + if (buf.id !== 0) { bytesPerSec = buf.frequency * buf.bytesPerSample * buf.channels; break; } diff --git a/test/openal/test_openal_error.c b/test/openal/test_openal_error.c index 024d06f882cf3..28599e06bb3c4 100644 --- a/test/openal/test_openal_error.c +++ b/test/openal/test_openal_error.c @@ -14,6 +14,30 @@ ALCdevice* device = NULL; ALCcontext* context = NULL; +void test_offsets_with_zero_buffer(void) { + // Test AL_SAMPLE_OFFSET and AL_BYTE_OFFSET when buffer 0 is queued before a real buffer + ALuint buf = 0; + alGenBuffers(1, &buf); + char dummy_data[2000] = {0}; + alBufferData(buf, AL_FORMAT_MONO16, dummy_data, sizeof(dummy_data), 44100); + + ALuint src = 0; + alGenSources(1, &src); + + ALuint bufs[2] = {0, buf}; + alSourceQueueBuffers(src, 2, bufs); + assert(alGetError() == AL_NO_ERROR); + + alSourcei(src, AL_SAMPLE_OFFSET, 100); + assert(alGetError() == AL_NO_ERROR); + + alSourcei(src, AL_BYTE_OFFSET, 200); + assert(alGetError() == AL_NO_ERROR); + + alDeleteSources(1, &src); + alDeleteBuffers(1, &buf); +} + int main(int argc, char* argv[]) { ALCboolean ret; @@ -39,6 +63,8 @@ int main(int argc, char* argv[]) { // Check that the error is reset after reading it. assert(alGetError() == AL_NO_ERROR); + test_offsets_with_zero_buffer(); + ret = alcMakeContextCurrent(NULL); assert(ret == ALC_TRUE); @@ -46,4 +72,3 @@ int main(int argc, char* argv[]) { alcCloseDevice(device); return 0; } -