/** * @license * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT */ //'use strict'; var LibraryOpenAL = { // ************************************************************************ // ** INTERNALS // ************************************************************************ $AL__deps: ['$MainLoop'], $AL: { // ------------------------------------------------------ // -- Constants // ------------------------------------------------------ QUEUE_INTERVAL: 25, QUEUE_LOOKAHEAD: 100.0 / 1000.0, DEVICE_NAME: 'Emscripten OpenAL', CAPTURE_DEVICE_NAME: 'Emscripten OpenAL capture', ALC_EXTENSIONS: { // TODO: 'ALC_EXT_EFX': true, 'ALC_EXT_capture': true, 'ALC_SOFT_pause_device': true, 'ALC_SOFT_HRTF': true }, AL_EXTENSIONS: { 'AL_EXT_float32': true, 'AL_SOFT_loop_points': true, 'AL_SOFT_source_length': true, 'AL_EXT_source_distance_model': true, 'AL_SOFT_source_spatialize': true }, // ------------------------------------------------------ // -- ALC Fields // ------------------------------------------------------ _alcErr: 0, get alcErr() { return this._alcErr; }, set alcErr(val) { // Errors should not be overwritten by later errors until they are cleared by a query. if (this._alcErr === {{{ cDefs.ALC_NO_ERROR }}} || val === {{{ cDefs.ALC_NO_ERROR }}}) { this._alcErr = val; } }, deviceRefCounts: {}, alcStringCache: {}, paused: false, // ------------------------------------------------------ // -- AL Fields // ------------------------------------------------------ stringCache: {}, contexts: {}, currentCtx: null, buffers: { // The zero buffer is legal to use, so create a placeholder for it '0': { id: 0, refCount: 0, audioBuf: null, frequency: 0, bytesPerSample: 2, channels: 1, length: 0 } }, paramArray: [], // Used to prevent allocating a new array for each param call _nextId: 1, newId: () => AL.freeIds.length > 0 ? AL.freeIds.pop() : AL._nextId++, freeIds: [], // ------------------------------------------------------ // -- Mixing Logic // ------------------------------------------------------ scheduleContextAudio: (ctx) => { // If we are animating using the requestAnimationFrame method, then the main loop does not run when in the background. // To give a perfect glitch-free audio stop when switching from foreground to background, we need to avoid updating // audio altogether when in the background, so detect that case and kill audio buffer streaming if so. if (MainLoop.timingMode === {{{ cDefs.EM_TIMING_RAF }}} && document['visibilityState'] != 'visible') { return; } for (var i in ctx.sources) { AL.scheduleSourceAudio(ctx.sources[i]); } }, // This function is the core scheduler that queues web-audio buffers for output. // src.bufQueue represents the abstract OpenAL buffer queue, which is traversed to schedule // corresponding web-audio buffers. These buffers are stored in src.audioQueue, which // represents the queue of buffers scheduled for physical playback. These two queues are // distinct because of the differing semantics of OpenAL and web audio. Some changes // to OpenAL parameters, such as pitch, may require the web audio queue to be flushed and rescheduled. scheduleSourceAudio: (src, lookahead) => { // See comment on scheduleContextAudio above. if (MainLoop.timingMode === {{{ cDefs.EM_TIMING_RAF }}} && document['visibilityState'] != 'visible') { return; } if (src.state !== {{{ cDefs.AL_PLAYING }}}) { return; } var currentTime = AL.updateSourceTime(src); var startTime = src.bufStartTime; var startOffset = src.bufOffset; var bufCursor = src.bufsProcessed; // Advance past any audio that is already scheduled for (var i = 0; i < src.audioQueue.length; i++) { var audioSrc = src.audioQueue[i]; startTime = audioSrc._startTime + audioSrc._duration; startOffset = 0.0; bufCursor += audioSrc._skipCount + 1; } if (!lookahead) { lookahead = AL.QUEUE_LOOKAHEAD; } var lookaheadTime = currentTime + lookahead; var skipCount = 0; while (startTime < lookaheadTime) { if (bufCursor >= src.bufQueue.length) { if (src.looping) { bufCursor %= src.bufQueue.length; } else { break; } } var buf = src.bufQueue[bufCursor % src.bufQueue.length]; // If the buffer contains no data, skip it if (buf.length === 0) { skipCount++; // If we've gone through the whole queue and everything is 0 length, just give up if (skipCount === src.bufQueue.length) { break; } } else { var audioSrc = src.context.audioCtx.createBufferSource(); audioSrc.buffer = buf.audioBuf; audioSrc.playbackRate.value = src.playbackRate; if (buf.audioBuf._loopStart || buf.audioBuf._loopEnd) { audioSrc.loopStart = buf.audioBuf._loopStart; audioSrc.loopEnd = buf.audioBuf._loopEnd; } var duration = 0.0; // If the source is a looping static buffer, use native looping for gapless playback if (src.type === {{{ cDefs.AL_STATIC }}} && src.looping) { duration = Number.POSITIVE_INFINITY; audioSrc.loop = true; if (buf.audioBuf._loopStart) { audioSrc.loopStart = buf.audioBuf._loopStart; } if (buf.audioBuf._loopEnd) { audioSrc.loopEnd = buf.audioBuf._loopEnd; } } else { duration = (buf.audioBuf.duration - startOffset) / src.playbackRate; } audioSrc._startOffset = startOffset; audioSrc._duration = duration; audioSrc._skipCount = skipCount; skipCount = 0; audioSrc.connect(src.gain); if (typeof audioSrc.start != 'undefined') { // Sample the current time as late as possible to mitigate drift startTime = Math.max(startTime, src.context.audioCtx.currentTime); audioSrc.start(startTime, startOffset); } else if (typeof audioSrc.noteOn != 'undefined') { startTime = Math.max(startTime, src.context.audioCtx.currentTime); audioSrc.noteOn(startTime); #if OPENAL_DEBUG if (offset > 0.0) { warnOnce('The current browser does not support AudioBufferSourceNode.start(when, offset); method, so cannot play back audio with an offset '+startOffset+' secs! Audio glitches will occur!'); } #endif } #if OPENAL_DEBUG else { warnOnce('Unable to start AudioBufferSourceNode playback! Not supported by the browser?'); } dbg(`scheduleSourceAudio() queuing buffer ${buf.id} for source ${src.id} at ${startTime} (offset by ${startOffset})`); #endif audioSrc._startTime = startTime; src.audioQueue.push(audioSrc); startTime += duration; } startOffset = 0.0; bufCursor++; } }, // Advance the state of a source forward to the current time updateSourceTime: (src) => { var currentTime = src.context.audioCtx.currentTime; if (src.state !== {{{ cDefs.AL_PLAYING }}}) { return currentTime; } // if the start time is unset, determine it based on the current offset. // This will be the case when a source is resumed after being paused, and // allows us to pretend that the source actually started playing some time // in the past such that it would just now have reached the stored offset. if (!isFinite(src.bufStartTime)) { src.bufStartTime = currentTime - src.bufOffset / src.playbackRate; src.bufOffset = 0.0; } var nextStartTime = 0.0; while (src.audioQueue.length) { var audioSrc = src.audioQueue[0]; src.bufsProcessed += audioSrc._skipCount; nextStartTime = audioSrc._startTime + audioSrc._duration; // n.b. audioSrc._duration already factors in playbackRate, so no divide by src.playbackRate on it. if (currentTime < nextStartTime) { break; } src.audioQueue.shift(); src.bufStartTime = nextStartTime; src.bufOffset = 0.0; src.bufsProcessed++; } if (src.bufsProcessed >= src.bufQueue.length && !src.looping) { // The source has played its entire queue and is non-looping, so just mark it as stopped. AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); } else if (src.type === {{{ cDefs.AL_STATIC }}} && src.looping) { // If the source is a looping static buffer, determine the buffer offset based on the loop points var buf = src.bufQueue[0]; if (buf.length === 0) { src.bufOffset = 0.0; } else { var delta = (currentTime - src.bufStartTime) * src.playbackRate; var loopStart = buf.audioBuf._loopStart || 0.0; var loopEnd = buf.audioBuf._loopEnd || buf.audioBuf.duration; if (loopEnd <= loopStart) { loopEnd = buf.audioBuf.duration; } if (delta < loopEnd) { src.bufOffset = delta; } else { src.bufOffset = loopStart + (delta - loopStart) % (loopEnd - loopStart); } } } else if (src.audioQueue[0]) { // The source is still actively playing, so we just need to calculate where we are in the current buffer // so it can be remembered if the source gets paused. src.bufOffset = (currentTime - src.audioQueue[0]._startTime) * src.playbackRate; } else { // The source hasn't finished yet, but there is no scheduled audio left for it. This can be because // the source has just been started/resumed, or due to an underrun caused by a long blocking operation. // We need to determine what state we would be in by this point in time so that when we next schedule // audio playback, it will be just as if no underrun occurred. if (src.type !== {{{ cDefs.AL_STATIC }}} && src.looping) { // if the source is a looping buffer queue, let's first calculate the queue duration, so we can // quickly fast forward past any full loops of the queue and only worry about the remainder. var srcDuration = AL.sourceDuration(src) / src.playbackRate; if (srcDuration > 0.0) { src.bufStartTime += Math.floor((currentTime - src.bufStartTime) / srcDuration) * srcDuration; } } // Since we've already skipped any full-queue loops if there were any, we just need to find // out where in the queue the remaining time puts us, which won't require stepping through the // entire queue more than once. for (var i = 0; i < src.bufQueue.length; i++) { if (src.bufsProcessed >= src.bufQueue.length) { if (src.looping) { src.bufsProcessed %= src.bufQueue.length; } else { AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); break; } } var buf = src.bufQueue[src.bufsProcessed]; if (buf.length > 0) { nextStartTime = src.bufStartTime + buf.audioBuf.duration / src.playbackRate; if (currentTime < nextStartTime) { src.bufOffset = (currentTime - src.bufStartTime) * src.playbackRate; break; } src.bufStartTime = nextStartTime; } src.bufOffset = 0.0; src.bufsProcessed++; } } return currentTime; }, cancelPendingSourceAudio: (src) => { AL.updateSourceTime(src); for (var i = 1; i < src.audioQueue.length; i++) { var audioSrc = src.audioQueue[i]; audioSrc.stop(); } if (src.audioQueue.length > 1) { src.audioQueue.length = 1; } }, stopSourceAudio: (src) => { for (var i = 0; i < src.audioQueue.length; i++) { src.audioQueue[i].stop(); } src.audioQueue.length = 0; }, setSourceState: (src, state) => { if (state === {{{ cDefs.AL_PLAYING }}}) { if (src.state === {{{ cDefs.AL_PLAYING }}} || src.state == {{{ cDefs.AL_STOPPED }}}) { src.bufsProcessed = 0; src.bufOffset = 0.0; #if OPENAL_DEBUG dbg(`setSourceState() resetting and playing source ${src.id}`); #endif } else { #if OPENAL_DEBUG dbg(`setSourceState() playing source ${src.id} at ${src.bufOffset}`); #endif } AL.stopSourceAudio(src); src.state = {{{ cDefs.AL_PLAYING }}}; src.bufStartTime = Number.NEGATIVE_INFINITY; AL.scheduleSourceAudio(src); } else if (state === {{{ cDefs.AL_PAUSED }}}) { if (src.state === {{{ cDefs.AL_PLAYING }}}) { // Store off the current offset to restore with on resume. AL.updateSourceTime(src); AL.stopSourceAudio(src); src.state = {{{ cDefs.AL_PAUSED }}}; #if OPENAL_DEBUG dbg(`setSourceState() pausing source ${src.id} at ${src.bufOffset}`); #endif } } else if (state === {{{ cDefs.AL_STOPPED }}}) { if (src.state !== {{{ cDefs.AL_INITIAL }}}) { src.state = {{{ cDefs.AL_STOPPED }}}; src.bufsProcessed = src.bufQueue.length; src.bufStartTime = Number.NEGATIVE_INFINITY; src.bufOffset = 0.0; AL.stopSourceAudio(src); #if OPENAL_DEBUG dbg(`setSourceState() stopping source ${src.id}`); #endif } } else if (state === {{{ cDefs.AL_INITIAL }}}) { if (src.state !== {{{ cDefs.AL_INITIAL }}}) { src.state = {{{ cDefs.AL_INITIAL }}}; src.bufsProcessed = 0; src.bufStartTime = Number.NEGATIVE_INFINITY; src.bufOffset = 0.0; AL.stopSourceAudio(src); #if OPENAL_DEBUG dbg(`setSourceState() initializing source ${src.id}`); #endif } } }, initSourcePanner: (src) => { if (src.type === 0x1030 /* AL_UNDETERMINED */) { return; } // Find the first non-zero buffer in the queue to determine the proper format var templateBuf = AL.buffers[0]; for (var i = 0; i < src.bufQueue.length; i++) { if (src.bufQueue[i].id !== 0) { templateBuf = src.bufQueue[i]; break; } } // Create a panner if AL_SOURCE_SPATIALIZE_SOFT is set to true, or alternatively if it's set to auto and the source is mono if (src.spatialize === {{{ cDefs.AL_TRUE }}} || (src.spatialize === 2 /* AL_AUTO_SOFT */ && templateBuf.channels === 1)) { if (src.panner) { return; } src.panner = src.context.audioCtx.createPanner(); AL.updateSourceGlobal(src); AL.updateSourceSpace(src); src.panner.connect(src.context.gain); src.gain.disconnect(); src.gain.connect(src.panner); } else { if (!src.panner) { return; } src.panner.disconnect(); src.gain.disconnect(); src.gain.connect(src.context.gain); src.panner = null; } }, updateContextGlobal: (ctx) => { for (var i in ctx.sources) { AL.updateSourceGlobal(ctx.sources[i]); } }, updateSourceGlobal: (src) => { var panner = src.panner; if (!panner) { return; } panner.refDistance = src.refDistance; panner.maxDistance = src.maxDistance; panner.rolloffFactor = src.rolloffFactor; panner.panningModel = src.context.hrtf ? 'HRTF' : 'equalpower'; // Use the source's distance model if AL_SOURCE_DISTANCE_MODEL is enabled var distanceModel = src.context.sourceDistanceModel ? src.distanceModel : src.context.distanceModel; switch (distanceModel) { case {{{ cDefs.AL_NONE }}}: panner.distanceModel = 'inverse'; panner.refDistance = 3.40282e38 /* FLT_MAX */; break; case 0xd001 /* AL_INVERSE_DISTANCE */: case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: panner.distanceModel = 'inverse'; break; case 0xd003 /* AL_LINEAR_DISTANCE */: case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: panner.distanceModel = 'linear'; break; case 0xd005 /* AL_EXPONENT_DISTANCE */: case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: panner.distanceModel = 'exponential'; break; } }, updateListenerSpace: (ctx) => { var listener = ctx.audioCtx.listener; if (listener.positionX) { listener.positionX.value = ctx.listener.position[0]; listener.positionY.value = ctx.listener.position[1]; listener.positionZ.value = ctx.listener.position[2]; } else { #if OPENAL_DEBUG warnOnce('Listener position attributes are not present, falling back to setPosition()'); #endif listener.setPosition(ctx.listener.position[0], ctx.listener.position[1], ctx.listener.position[2]); } if (listener.forwardX) { listener.forwardX.value = ctx.listener.direction[0]; listener.forwardY.value = ctx.listener.direction[1]; listener.forwardZ.value = ctx.listener.direction[2]; listener.upX.value = ctx.listener.up[0]; listener.upY.value = ctx.listener.up[1]; listener.upZ.value = ctx.listener.up[2]; } else { #if OPENAL_DEBUG warnOnce('Listener orientation attributes are not present, falling back to setOrientation()'); #endif listener.setOrientation( ctx.listener.direction[0], ctx.listener.direction[1], ctx.listener.direction[2], ctx.listener.up[0], ctx.listener.up[1], ctx.listener.up[2]); } // Update sources that are relative to the listener for (var i in ctx.sources) { AL.updateSourceSpace(ctx.sources[i]); } }, updateSourceSpace: (src) => { if (!src.panner) { return; } var panner = src.panner; var posX = src.position[0]; var posY = src.position[1]; var posZ = src.position[2]; var dirX = src.direction[0]; var dirY = src.direction[1]; var dirZ = src.direction[2]; var listener = src.context.listener; var lPosX = listener.position[0]; var lPosY = listener.position[1]; var lPosZ = listener.position[2]; // WebAudio does spatialization in world-space coordinates, meaning both the buffer sources and // the listener position are in the same absolute coordinate system relative to a fixed origin. // By default, OpenAL works this way as well, but it also provides a "listener relative" mode, where // a buffer source's coordinates are interpreted not in absolute world space, but as being relative // to the listener object itself, so as the listener moves the source appears to move with it // with no update required. Since web audio does not support this mode, we must transform the source // coordinates from listener-relative space to absolute world space. // // We do this via affine transformation matrices applied to the source position and source direction. // A change-of-basis converts from listener-space displacements to world-space displacements, // which must be done for both the source position and direction. Lastly, the source position must be // added to the listener position to get the final source position, since the source position represents // a displacement from the listener. if (src.relative) { // Negate the listener direction since forward is -Z. var lBackX = -listener.direction[0]; var lBackY = -listener.direction[1]; var lBackZ = -listener.direction[2]; var lUpX = listener.up[0]; var lUpY = listener.up[1]; var lUpZ = listener.up[2]; var inverseMagnitude = (x, y, z) => { var length = Math.sqrt(x * x + y * y + z * z); if (length < Number.EPSILON) { return 0.0; } return 1.0 / length; }; // Normalize the Back vector var invMag = inverseMagnitude(lBackX, lBackY, lBackZ); lBackX *= invMag; lBackY *= invMag; lBackZ *= invMag; // ...and the Up vector invMag = inverseMagnitude(lUpX, lUpY, lUpZ); lUpX *= invMag; lUpY *= invMag; lUpZ *= invMag; // Calculate the Right vector as the cross product of the Up and Back vectors var lRightX = (lUpY * lBackZ - lUpZ * lBackY); var lRightY = (lUpZ * lBackX - lUpX * lBackZ); var lRightZ = (lUpX * lBackY - lUpY * lBackX); // Back and Up might not be exactly perpendicular, so the cross product also needs normalization invMag = inverseMagnitude(lRightX, lRightY, lRightZ); lRightX *= invMag; lRightY *= invMag; lRightZ *= invMag; // Recompute Up from the now orthonormal Right and Back vectors so we have a fully orthonormal basis lUpX = (lBackY * lRightZ - lBackZ * lRightY); lUpY = (lBackZ * lRightX - lBackX * lRightZ); lUpZ = (lBackX * lRightY - lBackY * lRightX); var oldX = dirX; var oldY = dirY; var oldZ = dirZ; // Use our 3 vectors to apply a change-of-basis matrix to the source direction dirX = oldX * lRightX + oldY * lUpX + oldZ * lBackX; dirY = oldX * lRightY + oldY * lUpY + oldZ * lBackY; dirZ = oldX * lRightZ + oldY * lUpZ + oldZ * lBackZ; oldX = posX; oldY = posY; oldZ = posZ; // ...and to the source position posX = oldX * lRightX + oldY * lUpX + oldZ * lBackX; posY = oldX * lRightY + oldY * lUpY + oldZ * lBackY; posZ = oldX * lRightZ + oldY * lUpZ + oldZ * lBackZ; // The change-of-basis corrects the orientation, but the origin is still the listener. // Translate the source position by the listener position to finish. posX += lPosX; posY += lPosY; posZ += lPosZ; } if (panner.positionX) { // Assigning to panner.positionX/Y/Z unnecessarily seems to cause performance issues // See https://github.com/emscripten-core/emscripten/issues/15847 if (posX != panner.positionX.value) panner.positionX.value = posX; if (posY != panner.positionY.value) panner.positionY.value = posY; if (posZ != panner.positionZ.value) panner.positionZ.value = posZ; } else { #if OPENAL_DEBUG warnOnce('Panner position attributes are not present, falling back to setPosition()'); #endif panner.setPosition(posX, posY, posZ); } if (panner.orientationX) { // Assigning to panner.orientation/Y/Z unnecessarily seems to cause performance issues // See https://github.com/emscripten-core/emscripten/issues/15847 if (dirX != panner.orientationX.value) panner.orientationX.value = dirX; if (dirY != panner.orientationY.value) panner.orientationY.value = dirY; if (dirZ != panner.orientationZ.value) panner.orientationZ.value = dirZ; } else { #if OPENAL_DEBUG warnOnce('Panner orientation attributes are not present, falling back to setOrientation()'); #endif panner.setOrientation(dirX, dirY, dirZ); } var oldShift = src.dopplerShift; var velX = src.velocity[0]; var velY = src.velocity[1]; var velZ = src.velocity[2]; var lVelX = listener.velocity[0]; var lVelY = listener.velocity[1]; var lVelZ = listener.velocity[2]; if (posX === lPosX && posY === lPosY && posZ === lPosZ || velX === lVelX && velY === lVelY && velZ === lVelZ) { src.dopplerShift = 1.0; } else { // Doppler algorithm from 1.1 spec var speedOfSound = src.context.speedOfSound; var dopplerFactor = src.context.dopplerFactor; var slX = lPosX - posX; var slY = lPosY - posY; var slZ = lPosZ - posZ; var magSl = Math.sqrt(slX * slX + slY * slY + slZ * slZ); var vls = (slX * lVelX + slY * lVelY + slZ * lVelZ) / magSl; var vss = (slX * velX + slY * velY + slZ * velZ) / magSl; vls = Math.min(vls, speedOfSound / dopplerFactor); vss = Math.min(vss, speedOfSound / dopplerFactor); src.dopplerShift = (speedOfSound - dopplerFactor * vls) / (speedOfSound - dopplerFactor * vss); } if (src.dopplerShift !== oldShift) { AL.updateSourceRate(src); } }, updateSourceRate: (src) => { if (src.state === {{{ cDefs.AL_PLAYING }}}) { // clear scheduled buffers AL.cancelPendingSourceAudio(src); var audioSrc = src.audioQueue[0]; if (!audioSrc) { return; // It is possible that AL.scheduleContextAudio() has not yet fed the next buffer, if so, skip. } var duration; if (src.type === {{{ cDefs.AL_STATIC }}} && src.looping) { duration = Number.POSITIVE_INFINITY; } else { // audioSrc._duration is expressed after factoring in playbackRate, so when changing playback rate, need // to recompute/rescale the rate to the new playback speed. duration = (audioSrc.buffer.duration - audioSrc._startOffset) / src.playbackRate; } audioSrc._duration = duration; audioSrc.playbackRate.value = src.playbackRate; // reschedule buffers with the new playbackRate AL.scheduleSourceAudio(src); } }, sourceDuration: (src) => { var length = 0.0; for (var i = 0; i < src.bufQueue.length; i++) { var audioBuf = src.bufQueue[i].audioBuf; length += audioBuf ? audioBuf.duration : 0.0; } return length; }, sourceTell: (src) => { AL.updateSourceTime(src); var offset = 0.0; for (var i = 0; i < src.bufsProcessed; i++) { if (src.bufQueue[i].audioBuf) { offset += src.bufQueue[i].audioBuf.duration; } } offset += src.bufOffset; return offset; }, sourceSeek: (src, offset) => { var playing = src.state == {{{ cDefs.AL_PLAYING }}}; if (playing) { AL.setSourceState(src, {{{ cDefs.AL_INITIAL }}}); } if (src.bufQueue[src.bufsProcessed].audioBuf !== null) { src.bufsProcessed = 0; while (offset > src.bufQueue[src.bufsProcessed].audioBuf.duration) { offset -= src.bufQueue[src.bufsProcessed].audioBuf.duration; src.bufsProcessed++; } src.bufOffset = offset; } if (playing) { AL.setSourceState(src, {{{ cDefs.AL_PLAYING }}}); } }, // ------------------------------------------------------ // -- Accessor Helpers // ------------------------------------------------------ getGlobalParam: (funcname, param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return null; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: return AL.currentCtx.dopplerFactor; case {{{ cDefs.AL_SPEED_OF_SOUND }}}: return AL.currentCtx.speedOfSound; case {{{ cDefs.AL_DISTANCE_MODEL }}}: return AL.currentCtx.distanceModel; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return null; } }, setGlobalParam: (funcname, param, value) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: if (!Number.isFinite(value) || value < 0.0) { // Strictly negative values are disallowed #if OPENAL_DEBUG dbg(`${funcname}() value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.currentCtx.dopplerFactor = value; AL.updateListenerSpace(AL.currentCtx); break; case {{{ cDefs.AL_SPEED_OF_SOUND }}}: if (!Number.isFinite(value) || value <= 0.0) { // Negative or zero values are disallowed #if OPENAL_DEBUG dbg(`${funcname}() value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.currentCtx.speedOfSound = value; AL.updateListenerSpace(AL.currentCtx); break; case {{{ cDefs.AL_DISTANCE_MODEL }}}: switch (value) { case {{{ cDefs.AL_NONE }}}: case 0xd001 /* AL_INVERSE_DISTANCE */: case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: case 0xd003 /* AL_LINEAR_DISTANCE */: case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: case 0xd005 /* AL_EXPONENT_DISTANCE */: case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: AL.currentCtx.distanceModel = value; AL.updateContextGlobal(AL.currentCtx); break; default: #if OPENAL_DEBUG dbg(`${funcname}() value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } break; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, getListenerParam: (funcname, param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return null; } switch (param) { case {{{ cDefs.AL_POSITION }}}: return AL.currentCtx.listener.position; case {{{ cDefs.AL_VELOCITY }}}: return AL.currentCtx.listener.velocity; case {{{ cDefs.AL_ORIENTATION }}}: return AL.currentCtx.listener.direction.concat(AL.currentCtx.listener.up); case {{{ cDefs.AL_GAIN }}}: return AL.currentCtx.gain.gain.value; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return null; } }, setListenerParam: (funcname, param, value) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return; } if (value === null) { #if OPENAL_DEBUG dbg(`${funcname}(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } var listener = AL.currentCtx.listener; switch (param) { case {{{ cDefs.AL_POSITION }}}: if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_POSITION value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } listener.position[0] = value[0]; listener.position[1] = value[1]; listener.position[2] = value[2]; AL.updateListenerSpace(AL.currentCtx); break; case {{{ cDefs.AL_VELOCITY }}}: if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_VELOCITY value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } listener.velocity[0] = value[0]; listener.velocity[1] = value[1]; listener.velocity[2] = value[2]; AL.updateListenerSpace(AL.currentCtx); break; case {{{ cDefs.AL_GAIN }}}: if (!Number.isFinite(value) || value < 0.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_GAIN value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.currentCtx.gain.gain.value = value; break; case {{{ cDefs.AL_ORIENTATION }}}: if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2]) || !Number.isFinite(value[3]) || !Number.isFinite(value[4]) || !Number.isFinite(value[5]) ) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_ORIENTATION value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } listener.direction[0] = value[0]; listener.direction[1] = value[1]; listener.direction[2] = value[2]; listener.up[0] = value[3]; listener.up[1] = value[4]; listener.up[2] = value[5]; AL.updateListenerSpace(AL.currentCtx); break; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, getBufferParam: (funcname, bufferId, param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return; } var buf = AL.buffers[bufferId]; if (!buf || bufferId === 0) { #if OPENAL_DEBUG dbg(`${funcname}() called with an invalid buffer`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } switch (param) { case 0x2001 /* AL_FREQUENCY */: return buf.frequency; case 0x2002 /* AL_BITS */: return buf.bytesPerSample * 8; case 0x2003 /* AL_CHANNELS */: return buf.channels; case 0x2004 /* AL_SIZE */: return buf.length * buf.bytesPerSample * buf.channels; case 0x2015 /* AL_LOOP_POINTS_SOFT */: if (buf.length === 0) { return [0, 0]; } return [ (buf.audioBuf._loopStart || 0.0) * buf.frequency, (buf.audioBuf._loopEnd || buf.length) * buf.frequency ]; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return null; } }, setBufferParam: (funcname, bufferId, param, value) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return; } var buf = AL.buffers[bufferId]; if (!buf || bufferId === 0) { #if OPENAL_DEBUG dbg(`${funcname}() called with an invalid buffer`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } if (value === null) { #if OPENAL_DEBUG dbg(`${funcname}(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } switch (param) { case 0x2004 /* AL_SIZE */: if (value !== 0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_SIZE value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } // Per the spec, setting AL_SIZE to 0 is a legal NOP. break; case 0x2015 /* AL_LOOP_POINTS_SOFT */: if (value[0] < 0 || value[0] > buf.length || value[1] < 0 || value[1] > buf.Length || value[0] >= value[1]) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_LOOP_POINTS_SOFT value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } if (buf.refCount > 0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_LOOP_POINTS_SOFT set on bound buffer`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; return; } if (buf.audioBuf) { buf.audioBuf._loopStart = value[0] / buf.frequency; buf.audioBuf._loopEnd = value[1] / buf.frequency; } break; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)}' is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, getSourceParam: (funcname, sourceId, param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return null; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg(`${funcname}() called with an invalid source`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return null; } switch (param) { case 0x202 /* AL_SOURCE_RELATIVE */: return src.relative; case 0x1001 /* AL_CONE_INNER_ANGLE */: return src.coneInnerAngle; case 0x1002 /* AL_CONE_OUTER_ANGLE */: return src.coneOuterAngle; case 0x1003 /* AL_PITCH */: return src.pitch; case {{{ cDefs.AL_POSITION }}}: return src.position; case {{{ cDefs.AL_DIRECTION }}}: return src.direction; case {{{ cDefs.AL_VELOCITY }}}: return src.velocity; case 0x1007 /* AL_LOOPING */: return src.looping; case 0x1009 /* AL_BUFFER */: if (src.type === {{{ cDefs.AL_STATIC }}}) { return src.bufQueue[0].id; } return 0; case {{{ cDefs.AL_GAIN }}}: return src.gain.gain.value; case 0x100D /* AL_MIN_GAIN */: return src.minGain; case 0x100E /* AL_MAX_GAIN */: return src.maxGain; case 0x1010 /* AL_SOURCE_STATE */: return src.state; case 0x1015 /* AL_BUFFERS_QUEUED */: if (src.bufQueue.length === 1 && src.bufQueue[0].id === 0) { return 0; } return src.bufQueue.length; case 0x1016 /* AL_BUFFERS_PROCESSED */: if ((src.bufQueue.length === 1 && src.bufQueue[0].id === 0) || src.looping) { return 0; } return src.bufsProcessed; case 0x1020 /* AL_REFERENCE_DISTANCE */: return src.refDistance; case 0x1021 /* AL_ROLLOFF_FACTOR */: return src.rolloffFactor; case 0x1022 /* AL_CONE_OUTER_GAIN */: return src.coneOuterGain; case 0x1023 /* AL_MAX_DISTANCE */: return src.maxDistance; case 0x1024 /* AL_SEC_OFFSET */: return AL.sourceTell(src); case 0x1025 /* AL_SAMPLE_OFFSET */: var offset = AL.sourceTell(src); if (offset > 0.0) { offset *= src.bufQueue[0].frequency; } return offset; case 0x1026 /* AL_BYTE_OFFSET */: var offset = AL.sourceTell(src); if (offset > 0.0) { offset *= src.bufQueue[0].frequency * src.bufQueue[0].bytesPerSample; } return offset; case 0x1027 /* AL_SOURCE_TYPE */: return src.type; case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: return src.spatialize; case 0x2009 /* AL_BYTE_LENGTH_SOFT */: var length = 0; var bytesPerFrame = 0; for (var i = 0; i < src.bufQueue.length; i++) { length += src.bufQueue[i].length; if (src.bufQueue[i].id !== 0) { bytesPerFrame = src.bufQueue[i].bytesPerSample * src.bufQueue[i].channels; } } return length * bytesPerFrame; case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: var length = 0; for (var i = 0; i < src.bufQueue.length; i++) { length += src.bufQueue[i].length; } return length; case 0x200B /* AL_SEC_LENGTH_SOFT */: return AL.sourceDuration(src); case {{{ cDefs.AL_DISTANCE_MODEL }}}: return src.distanceModel; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)}' is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return null; } }, setSourceParam: (funcname, sourceId, param, value) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg(`${funcname}() called without a valid context`); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourcef() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } if (value === null) { #if OPENAL_DEBUG dbg(`${funcname}(): param ${ptrToString(param)}' has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } switch (param) { case 0x202 /* AL_SOURCE_RELATIVE */: if (value === {{{ cDefs.AL_TRUE }}}) { src.relative = true; AL.updateSourceSpace(src); } else if (value === {{{ cDefs.AL_FALSE }}}) { src.relative = false; AL.updateSourceSpace(src); } else { #if OPENAL_DEBUG dbg(`${funcname}() param AL_SOURCE_RELATIVE value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } break; case 0x1001 /* AL_CONE_INNER_ANGLE */: if (!Number.isFinite(value)) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_CONE_INNER_ANGLE value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.coneInnerAngle = value; if (src.panner) { src.panner.coneInnerAngle = value % 360.0; } break; case 0x1002 /* AL_CONE_OUTER_ANGLE */: if (!Number.isFinite(value)) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_CONE_OUTER_ANGLE value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.coneOuterAngle = value; if (src.panner) { src.panner.coneOuterAngle = value % 360.0; } break; case 0x1003 /* AL_PITCH */: if (!Number.isFinite(value) || value <= 0.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_PITCH value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } if (src.pitch === value) { break; } src.pitch = value; AL.updateSourceRate(src); break; case {{{ cDefs.AL_POSITION }}}: if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_POSITION value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.position[0] = value[0]; src.position[1] = value[1]; src.position[2] = value[2]; AL.updateSourceSpace(src); break; case {{{ cDefs.AL_DIRECTION }}}: if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_DIRECTION value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.direction[0] = value[0]; src.direction[1] = value[1]; src.direction[2] = value[2]; AL.updateSourceSpace(src); break; case {{{ cDefs.AL_VELOCITY }}}: if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_VELOCITY value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.velocity[0] = value[0]; src.velocity[1] = value[1]; src.velocity[2] = value[2]; AL.updateSourceSpace(src); break; case 0x1007 /* AL_LOOPING */: if (value === {{{ cDefs.AL_TRUE }}}) { src.looping = true; AL.updateSourceTime(src); if (src.type === {{{ cDefs.AL_STATIC }}} && src.audioQueue.length > 0) { var audioSrc = src.audioQueue[0]; audioSrc.loop = true; audioSrc._duration = Number.POSITIVE_INFINITY; } } else if (value === {{{ cDefs.AL_FALSE }}}) { src.looping = false; var currentTime = AL.updateSourceTime(src); if (src.type === {{{ cDefs.AL_STATIC }}} && src.audioQueue.length > 0) { var audioSrc = src.audioQueue[0]; audioSrc.loop = false; audioSrc._duration = src.bufQueue[0].audioBuf.duration / src.playbackRate; audioSrc._startTime = currentTime - src.bufOffset / src.playbackRate; } } else { #if OPENAL_DEBUG dbg(`${funcname}() param AL_LOOPING value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } break; case 0x1009 /* AL_BUFFER */: if (src.state === {{{ cDefs.AL_PLAYING }}} || src.state === {{{ cDefs.AL_PAUSED }}}) { #if OPENAL_DEBUG dbg(`${funcname}(AL_BUFFER) called while source is playing or paused`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; return; } if (value === 0) { for (var i in src.bufQueue) { src.bufQueue[i].refCount--; } src.bufQueue.length = 1; src.bufQueue[0] = AL.buffers[0]; src.bufsProcessed = 0; src.type = 0x1030 /* AL_UNDETERMINED */; } else { var buf = AL.buffers[value]; if (!buf) { #if OPENAL_DEBUG dbg('alSourcei(AL_BUFFER) called with an invalid buffer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } for (var i in src.bufQueue) { src.bufQueue[i].refCount--; } src.bufQueue.length = 0; buf.refCount++; src.bufQueue = [buf]; src.bufsProcessed = 0; src.type = {{{ cDefs.AL_STATIC }}}; } AL.initSourcePanner(src); AL.scheduleSourceAudio(src); break; case {{{ cDefs.AL_GAIN }}}: if (!Number.isFinite(value) || value < 0.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_GAIN value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.gain.gain.value = value; break; case 0x100D /* AL_MIN_GAIN */: if (!Number.isFinite(value) || value < 0.0 || value > Math.min(src.maxGain, 1.0)) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_MIN_GAIN value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG warnOnce('AL_MIN_GAIN is not currently supported'); #endif src.minGain = value; break; case 0x100E /* AL_MAX_GAIN */: if (!Number.isFinite(value) || value < Math.max(0.0, src.minGain) || value > 1.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_MAX_GAIN value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG warnOnce('AL_MAX_GAIN is not currently supported'); #endif src.maxGain = value; break; case 0x1020 /* AL_REFERENCE_DISTANCE */: if (!Number.isFinite(value) || value < 0.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_REFERENCE_DISTANCE value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.refDistance = value; if (src.panner) { src.panner.refDistance = value; } break; case 0x1021 /* AL_ROLLOFF_FACTOR */: if (!Number.isFinite(value) || value < 0.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_ROLLOFF_FACTOR value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.rolloffFactor = value; if (src.panner) { src.panner.rolloffFactor = value; } break; case 0x1022 /* AL_CONE_OUTER_GAIN */: if (!Number.isFinite(value) || value < 0.0 || value > 1.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_CORE_OUTER_GAIN value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.coneOuterGain = value; if (src.panner) { src.panner.coneOuterGain = value; } break; case 0x1023 /* AL_MAX_DISTANCE */: if (!Number.isFinite(value) || value < 0.0) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_MAX_DISTANCE value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.maxDistance = value; if (src.panner) { src.panner.maxDistance = value; } break; case 0x1024 /* AL_SEC_OFFSET */: if (value < 0.0 || value > AL.sourceDuration(src)) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_SEC_OFFSET value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.sourceSeek(src, value); break; case 0x1025 /* AL_SAMPLE_OFFSET */: var srcLen = AL.sourceDuration(src); if (srcLen > 0.0) { var frequency; for (var bufId in src.bufQueue) { if (bufId) { frequency = src.bufQueue[bufId].frequency; break; } } value /= frequency; } if (value < 0.0 || value > srcLen) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_SAMPLE_OFFSET value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.sourceSeek(src, value); break; case 0x1026 /* AL_BYTE_OFFSET */: var srcLen = AL.sourceDuration(src); if (srcLen > 0.0) { var bytesPerSec; for (var bufId in src.bufQueue) { if (bufId) { var buf = src.bufQueue[bufId]; bytesPerSec = buf.frequency * buf.bytesPerSample * buf.channels; break; } } value /= bytesPerSec; } if (value < 0.0 || value > srcLen) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_BYTE_OFFSET value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.sourceSeek(src, value); break; case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: if (value !== {{{ cDefs.AL_FALSE }}} && value !== {{{ cDefs.AL_TRUE }}} && value !== 2 /* AL_AUTO_SOFT */) { #if OPENAL_DEBUG dbg(`${funcname}() param AL_SOURCE_SPATIALIZE_SOFT value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } src.spatialize = value; AL.initSourcePanner(src); break; case 0x2009 /* AL_BYTE_LENGTH_SOFT */: case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: case 0x200B /* AL_SEC_LENGTH_SOFT */: #if OPENAL_DEBUG dbg(`${funcname}() param AL_*_LENGTH_SOFT is read only`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; break; case {{{ cDefs.AL_DISTANCE_MODEL }}}: switch (value) { case {{{ cDefs.AL_NONE }}}: case 0xd001 /* AL_INVERSE_DISTANCE */: case 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */: case 0xd003 /* AL_LINEAR_DISTANCE */: case 0xd004 /* AL_LINEAR_DISTANCE_CLAMPED */: case 0xd005 /* AL_EXPONENT_DISTANCE */: case 0xd006 /* AL_EXPONENT_DISTANCE_CLAMPED */: src.distanceModel = value; if (AL.currentCtx.sourceDistanceModel) { AL.updateContextGlobal(AL.currentCtx); } break; default: #if OPENAL_DEBUG dbg(`${funcname}() param AL_DISTANCE_MODEL value ${value} is out of range`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } break; default: #if OPENAL_DEBUG dbg(`${funcname}() param ${ptrToString(param)} is unknown or not implemented`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, // ------------------------------------------------------- // -- Capture // ------------------------------------------------------- // A map of 'capture device contexts'. captures: {}, sharedCaptureAudioCtx: null, // Helper which: // - Asserts that deviceId is both non-NULL AND a known device ID; // - Returns a reference to it, or null if not found. // - Sets alcErr accordingly. // Treat NULL and separately because careless // people might assume that most alcCapture functions // accept NULL as a 'use the default' device. requireValidCaptureDevice: (deviceId, funcname) => { if (deviceId === 0) { #if OPENAL_DEBUG dbg(`${funcname}() on a NULL device is an error`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return null; } var c = AL.captures[deviceId]; if (!c) { #if OPENAL_DEBUG dbg(`${funcname}() on an invalid device`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return null; } var err = c.mediaStreamError; if (err) { #if OPENAL_DEBUG switch (err.name) { case 'PermissionDeniedError': dbg(`${funcname}() but the user denied access to the device`); break; case 'NotFoundError': dbg(`${funcname}() but no capture device was found`); break; default: dbg(`${funcname}() but a MediaStreamError was encountered: ${err}`); break; } #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return null; } return c; } }, // *************************************************************************** // ** ALC API // *************************************************************************** // ------------------------------------------------------- // -- ALC Capture // ------------------------------------------------------- // bufferSize is actually 'number of sample frames', so was renamed // bufferFrameCapacity here for clarity. alcCaptureOpenDevice__deps: ['$autoResumeAudioContext'], alcCaptureOpenDevice__proxy: 'sync', alcCaptureOpenDevice: (pDeviceName, requestedSampleRate, format, bufferFrameCapacity) => { var resolvedDeviceName = AL.CAPTURE_DEVICE_NAME; // NULL is a valid device name here (resolves to default); if (pDeviceName !== 0) { resolvedDeviceName = UTF8ToString(pDeviceName); if (resolvedDeviceName !== AL.CAPTURE_DEVICE_NAME) { #if OPENAL_DEBUG dbg(`alcCaptureOpenDevice() with invalid device name '${resolvedDeviceName}'`); #endif // ALC_OUT_OF_MEMORY // From the programmer's guide, ALC_OUT_OF_MEMORY's meaning is // overloaded here, to mean: // 'The specified device is invalid, or can not capture audio.' // This may be misleading to API users, but well... AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; return 0; } } // Otherwise it's probably okay (though useless) for bufferFrameCapacity to be zero. if (bufferFrameCapacity < 0) { // ALCsizei is signed int #if OPENAL_DEBUG dbg('alcCaptureOpenDevice() with negative bufferSize'); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return 0; } navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; var has_getUserMedia = navigator.getUserMedia || (navigator.mediaDevices && navigator.mediaDevices.getUserMedia); if (!has_getUserMedia) { #if OPENAL_DEBUG dbg('alcCaptureOpenDevice() cannot capture audio, because your browser lacks a `getUserMedia()` implementation'); #endif // See previously mentioned rationale for ALC_OUT_OF_MEMORY AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; return 0; } var AudioContext = window.AudioContext || window.webkitAudioContext; if (!AL.sharedCaptureAudioCtx) { try { AL.sharedCaptureAudioCtx = new AudioContext(); } catch(e) { #if OPENAL_DEBUG dbg(`alcCaptureOpenDevice() could not create the shared capture AudioContext: ${e}`); #endif // See previously mentioned rationale for ALC_OUT_OF_MEMORY AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; return 0; } } autoResumeAudioContext(AL.sharedCaptureAudioCtx); var outputChannelCount; switch (format) { case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ case 0x1101: /* AL_FORMAT_MONO16 */ case 0x1100: /* AL_FORMAT_MONO8 */ outputChannelCount = 1; break; case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ case 0x1103: /* AL_FORMAT_STEREO16 */ case 0x1102: /* AL_FORMAT_STEREO8 */ outputChannelCount = 2; break; default: #if OPENAL_DEBUG dbg(`alcCaptureOpenDevice() with unsupported format ${format}`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return 0; } function newF32Array(cap) { return new Float32Array(cap);} function newI16Array(cap) { return new Int16Array(cap); } function newU8Array(cap) { return new Uint8Array(cap); } var requestedSampleType; var newSampleArray; switch (format) { case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ requestedSampleType = 'f32'; newSampleArray = newF32Array; break; case 0x1101: /* AL_FORMAT_MONO16 */ case 0x1103: /* AL_FORMAT_STEREO16 */ requestedSampleType = 'i16'; newSampleArray = newI16Array; break; case 0x1100: /* AL_FORMAT_MONO8 */ case 0x1102: /* AL_FORMAT_STEREO8 */ requestedSampleType = 'u8'; newSampleArray = newU8Array; break; } var buffers = []; try { for (var chan=0; chan < outputChannelCount; ++chan) { buffers[chan] = newSampleArray(bufferFrameCapacity); } } catch(e) { #if OPENAL_DEBUG dbg(`alcCaptureOpenDevice() failed to allocate internal buffers (is bufferSize low enough?): ${e}`); #endif AL.alcErr = 0xA005 /* ALC_OUT_OF_MEMORY */; return 0; } // What we'll place into the `AL.captures` array in the end, // declared here for closures to access it var newCapture = { audioCtx: AL.sharedCaptureAudioCtx, deviceName: resolvedDeviceName, requestedSampleRate, requestedSampleType, outputChannelCount, inputChannelCount: null, // Not known until the getUserMedia() promise resolves mediaStreamError: null, // Used by other functions to return early and report an error. mediaStreamSourceNode: null, mediaStream: null, // Either one, or none of the below two, is active. mergerNode: null, splitterNode: null, scriptProcessorNode: null, isCapturing: false, buffers, get bufferFrameCapacity() { return buffers[0].length; }, capturePlayhead: 0, // current write position, in sample frames captureReadhead: 0, capturedFrameCount: 0 }; // Preparing for getUserMedia() var onError = (mediaStreamError) => { newCapture.mediaStreamError = mediaStreamError; #if OPENAL_DEBUG dbg(`navigator.getUserMedia() errored with: ${mediaStreamError}`); #endif }; var onSuccess = (mediaStream) => { newCapture.mediaStreamSourceNode = newCapture.audioCtx.createMediaStreamSource(mediaStream); newCapture.mediaStream = mediaStream; var inputChannelCount = 1; switch (newCapture.mediaStreamSourceNode.channelCountMode) { case 'max': inputChannelCount = outputChannelCount; break; case 'clamped-max': inputChannelCount = Math.min(outputChannelCount, newCapture.mediaStreamSourceNode.channelCount); break; case 'explicit': inputChannelCount = newCapture.mediaStreamSourceNode.channelCount; break; } newCapture.inputChannelCount = inputChannelCount; #if OPENAL_DEBUG if (inputChannelCount > 2 || outputChannelCount > 2) { dbg('The number of input or output channels is too high, capture might not work as expected!'); } #endif // Have to pick a size from 256, 512, 1024, 2048, 4096, 8192, 16384. // One can also set it to zero, which leaves the decision up to the impl. // An extension could allow specifying this value. var processorFrameCount = 512; newCapture.scriptProcessorNode = newCapture.audioCtx.createScriptProcessor( processorFrameCount, inputChannelCount, outputChannelCount ); if (inputChannelCount > outputChannelCount) { newCapture.mergerNode = newCapture.audioCtx.createChannelMerger(inputChannelCount); newCapture.mediaStreamSourceNode.connect(newCapture.mergerNode); newCapture.mergerNode.connect(newCapture.scriptProcessorNode); } else if (inputChannelCount < outputChannelCount) { newCapture.splitterNode = newCapture.audioCtx.createChannelSplitter(outputChannelCount); newCapture.mediaStreamSourceNode.connect(newCapture.splitterNode); newCapture.splitterNode.connect(newCapture.scriptProcessorNode); } else { newCapture.mediaStreamSourceNode.connect(newCapture.scriptProcessorNode); } newCapture.scriptProcessorNode.connect(newCapture.audioCtx.destination); newCapture.scriptProcessorNode.onaudioprocess = (audioProcessingEvent) => { if (!newCapture.isCapturing) { return; } var c = newCapture; var srcBuf = audioProcessingEvent.inputBuffer; // Actually just copy srcBuf's channel data into // c.buffers, optimizing for each case. switch (format) { case 0x10010: /* AL_FORMAT_MONO_FLOAT32 */ var channel0 = srcBuf.getChannelData(0); for (var i = 0 ; i < srcBuf.length; ++i) { var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; c.buffers[0][wi] = channel0[i]; } break; case 0x10011: /* AL_FORMAT_STEREO_FLOAT32 */ var channel0 = srcBuf.getChannelData(0); var channel1 = srcBuf.getChannelData(1); for (var i = 0 ; i < srcBuf.length; ++i) { var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; c.buffers[0][wi] = channel0[i]; c.buffers[1][wi] = channel1[i]; } break; case 0x1101: /* AL_FORMAT_MONO16 */ var channel0 = srcBuf.getChannelData(0); for (var i = 0 ; i < srcBuf.length; ++i) { var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; c.buffers[0][wi] = channel0[i] * 32767; } break; case 0x1103: /* AL_FORMAT_STEREO16 */ var channel0 = srcBuf.getChannelData(0); var channel1 = srcBuf.getChannelData(1); for (var i = 0 ; i < srcBuf.length; ++i) { var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; c.buffers[0][wi] = channel0[i] * 32767; c.buffers[1][wi] = channel1[i] * 32767; } break; case 0x1100: /* AL_FORMAT_MONO8 */ var channel0 = srcBuf.getChannelData(0); for (var i = 0 ; i < srcBuf.length; ++i) { var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; c.buffers[0][wi] = (channel0[i] + 1.0) * 127; } break; case 0x1102: /* AL_FORMAT_STEREO8 */ var channel0 = srcBuf.getChannelData(0); var channel1 = srcBuf.getChannelData(1); for (var i = 0 ; i < srcBuf.length; ++i) { var wi = (c.capturePlayhead + i) % c.bufferFrameCapacity; c.buffers[0][wi] = (channel0[i] + 1.0) * 127; c.buffers[1][wi] = (channel1[i] + 1.0) * 127; } break; } c.capturePlayhead += srcBuf.length; c.capturePlayhead %= c.bufferFrameCapacity; c.capturedFrameCount += srcBuf.length; c.capturedFrameCount = Math.min(c.capturedFrameCount, c.bufferFrameCapacity); }; }; // The latest way to call getUserMedia() if (navigator.mediaDevices?.getUserMedia) { navigator.mediaDevices .getUserMedia({audio: true}) .then(onSuccess) .catch(onError); } else { // The usual (now deprecated) way navigator.getUserMedia({audio: true}, onSuccess, onError); } var id = AL.newId(); AL.captures[id] = newCapture; return id; }, alcCaptureCloseDevice__proxy: 'sync', alcCaptureCloseDevice: (deviceId) => { var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureCloseDevice'); if (!c) return false; delete AL.captures[deviceId]; AL.freeIds.push(deviceId); // This clean-up might be unnecessary (paranoid) ? // May happen if user hasn't decided to grant or deny input c.mediaStreamSourceNode?.disconnect(); c.mergerNode?.disconnect(); c.splitterNode?.disconnect(); // May happen if user hasn't decided to grant or deny input c.scriptProcessorNode?.disconnect(); if (c.mediaStream) { // Disabling the microphone of the browser. // Without this operation, the red dot on the browser tab page will remain. c.mediaStream.getTracks().forEach((track) => track.stop()); } delete c.buffers; c.capturedFrameCount = 0; c.isCapturing = false; return true; }, alcCaptureStart__proxy: 'sync', alcCaptureStart: (deviceId) => { var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStart'); if (!c) return; if (c.isCapturing) { #if OPENAL_DEBUG dbg('Redundant call to alcCaptureStart()'); #endif // NOTE: Spec says (emphasis mine): // The amount of audio samples available after **restarting** a // stopped capture device is reset to zero. // So redundant calls to alcCaptureStart() must have no effect. return; } c.isCapturing = true; c.capturedFrameCount = 0; c.capturePlayhead = 0; }, alcCaptureStop__proxy: 'sync', alcCaptureStop: (deviceId) => { var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStop'); if (!c) return; #if OPENAL_DEBUG if (!c.isCapturing) { dbg('Redundant call to alcCaptureStop()'); } #endif c.isCapturing = false; }, // The OpenAL spec hints that implementations are allowed to // 'defer resampling and other conversions' up until this point. // // The last parameter is actually 'number of sample frames', so was // renamed accordingly here alcCaptureSamples__proxy: 'sync', alcCaptureSamples: (deviceId, pFrames, requestedFrameCount) => { var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureSamples'); if (!c) return; // ALCsizei is actually 32-bit signed int, so could be negative // Also, spec says : // Requesting more sample frames than are currently available is // an error. var dstfreq = c.requestedSampleRate; var srcfreq = c.audioCtx.sampleRate; var fratio = srcfreq / dstfreq; if (requestedFrameCount < 0 || requestedFrameCount > (c.capturedFrameCount / fratio)) { #if OPENAL_DEBUG dbg('alcCaptureSamples() with invalid bufferSize'); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return; } function setF32Sample(i, sample) { {{{ makeSetValue('pFrames', '4*i', 'sample', 'float') }}}; } function setI16Sample(i, sample) { {{{ makeSetValue('pFrames', '2*i', 'sample', 'i16') }}}; } function setU8Sample(i, sample) { {{{ makeSetValue('pFrames', 'i', 'sample', 'i8') }}}; } var setSample; switch (c.requestedSampleType) { case 'f32': setSample = setF32Sample; break; case 'i16': setSample = setI16Sample; break; case 'u8' : setSample = setU8Sample ; break; default: #if OPENAL_DEBUG dbg(`Internal error: Unknown sample type '${c.requestedSampleType}'`); #endif return; } // If fratio is an integer we don't need linear resampling, just skip samples if (Math.floor(fratio) == fratio) { for (var i = 0, frame_i = 0; frame_i < requestedFrameCount; ++frame_i) { for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { setSample(i, c.buffers[chan][c.captureReadhead]); } c.captureReadhead = (fratio + c.captureReadhead) % c.bufferFrameCapacity; } } else { // Perform linear resampling. // There is room for improvement - right now we're fine with linear resampling. // We don't use OfflineAudioContexts for this: See the discussion at // https://github.com/jpernst/emscripten/issues/2#issuecomment-312729735 // if you're curious about why. for (var i = 0, frame_i = 0; frame_i < requestedFrameCount; ++frame_i) { var lefti = Math.floor(c.captureReadhead); var righti = Math.ceil(c.captureReadhead); var d = c.captureReadhead - lefti; for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { var lefts = c.buffers[chan][lefti]; var rights = c.buffers[chan][righti]; setSample(i, (1 - d) * lefts + d * rights); } c.captureReadhead = (c.captureReadhead + fratio) % c.bufferFrameCapacity; } } // Spec doesn't say if alcCaptureSamples() must zero the number // of available captured sample-frames, but not only would it // be insane not to do, OpenAL-Soft happens to do that as well. c.capturedFrameCount = 0; }, // ------------------------------------------------------- // -- ALC Resources // ------------------------------------------------------- alcOpenDevice__proxy: 'sync', alcOpenDevice: (pDeviceName) => { if (pDeviceName) { var name = UTF8ToString(pDeviceName); if (name !== AL.DEVICE_NAME) { return 0; } } if (globalThis.AudioContext || globalThis.webkitAudioContext) { var deviceId = AL.newId(); AL.deviceRefCounts[deviceId] = 0; return deviceId; } return 0; }, alcCloseDevice__proxy: 'sync', alcCloseDevice: (deviceId) => { if (!(deviceId in AL.deviceRefCounts) || AL.deviceRefCounts[deviceId] > 0) { return {{{ cDefs.ALC_FALSE }}}; } delete AL.deviceRefCounts[deviceId]; AL.freeIds.push(deviceId); return {{{ cDefs.ALC_TRUE }}}; }, alcCreateContext__deps: ['$autoResumeAudioContext'], alcCreateContext__proxy: 'sync', alcCreateContext: (deviceId, pAttrList) => { if (!(deviceId in AL.deviceRefCounts)) { #if OPENAL_DEBUG dbg('alcCreateContext() called with an invalid device'); #endif AL.alcErr = 0xA001; /* ALC_INVALID_DEVICE */ return 0; } var options = null; var attrs = []; var hrtf = null; pAttrList >>= 2; if (pAttrList) { var attr = 0; var val = 0; while (true) { attr = HEAP32[pAttrList++]; attrs.push(attr); if (attr === 0) { break; } val = HEAP32[pAttrList++]; attrs.push(val); switch (attr) { case 0x1007 /* ALC_FREQUENCY */: if (!options) { options = {}; } options.sampleRate = val; break; case 0x1010 /* ALC_MONO_SOURCES */: // fallthrough case 0x1011 /* ALC_STEREO_SOURCES */: // Do nothing; these hints are satisfied by default break case 0x1992 /* ALC_HRTF_SOFT */: switch (val) { case {{{ cDefs.ALC_FALSE }}}: hrtf = false; break; case {{{ cDefs.ALC_TRUE }}}: hrtf = true; break; case 2 /* ALC_DONT_CARE_SOFT */: break; default: #if OPENAL_DEBUG dbg(`Unsupported ALC_HRTF_SOFT mode ${val}`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return 0; } break; case 0x1996 /* ALC_HRTF_ID_SOFT */: if (val !== 0) { #if OPENAL_DEBUG dbg(`Invalid ALC_HRTF_ID_SOFT index ${val}`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return 0; } break; default: #if OPENAL_DEBUG dbg(`Unsupported context attribute ${ptrToString(attr)}`); #endif AL.alcErr = 0xA004; /* ALC_INVALID_VALUE */ return 0; } } } var AudioContext = window.AudioContext || window.webkitAudioContext; var ac = null; try { // Only try to pass options if there are any, for compat with browsers that don't support this if (options) { ac = new AudioContext(options); } else { ac = new AudioContext(); } } catch (e) { if (e.name === 'NotSupportedError') { #if OPENAL_DEBUG dbg('Invalid or unsupported options'); #endif AL.alcErr = 0xA004; /* ALC_INVALID_VALUE */ } else { AL.alcErr = 0xA001; /* ALC_INVALID_DEVICE */ } return 0; } autoResumeAudioContext(ac); // Old Web Audio API (e.g. Safari 6.0.5) had an inconsistently named createGainNode function. if (typeof ac.createGain == 'undefined') { ac.createGain = ac.createGainNode; } var gain = ac.createGain(); gain.connect(ac.destination); var ctx = { deviceId, id: AL.newId(), attrs, audioCtx: ac, listener: { position: [0.0, 0.0, 0.0], velocity: [0.0, 0.0, 0.0], direction: [0.0, 0.0, 0.0], up: [0.0, 0.0, 0.0] }, sources: [], interval: setInterval(() => AL.scheduleContextAudio(ctx), AL.QUEUE_INTERVAL), gain, distanceModel: 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */, speedOfSound: 343.3, dopplerFactor: 1.0, sourceDistanceModel: false, hrtf: hrtf || false, _err: 0, get err() { return this._err; }, set err(val) { // Errors should not be overwritten by later errors until they are cleared by a query. if (this._err === {{{ cDefs.AL_NO_ERROR }}} || val === {{{ cDefs.AL_NO_ERROR }}}) { this._err = val; } } }; AL.deviceRefCounts[deviceId]++; AL.contexts[ctx.id] = ctx; if (hrtf !== null) { // Apply hrtf attrib to all contexts for this device for (var ctxId in AL.contexts) { var c = AL.contexts[ctxId]; if (c.deviceId === deviceId) { c.hrtf = hrtf; AL.updateContextGlobal(c); } } } return ctx.id; }, alcDestroyContext__proxy: 'sync', alcDestroyContext: (contextId) => { var ctx = AL.contexts[contextId]; if (AL.currentCtx === ctx) { #if OPENAL_DEBUG dbg('alcDestroyContext() called with an invalid context'); #endif AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; return; } // Stop playback, etc if (AL.contexts[contextId].interval) { clearInterval(AL.contexts[contextId].interval); } AL.deviceRefCounts[ctx.deviceId]--; delete AL.contexts[contextId]; AL.freeIds.push(contextId); }, // ------------------------------------------------------- // -- ALC State // ------------------------------------------------------- alcGetError__proxy: 'sync', alcGetError: (deviceId) => { var err = AL.alcErr; AL.alcErr = {{{ cDefs.ALC_NO_ERROR }}}; return err; }, alcGetCurrentContext__proxy: 'sync', alcGetCurrentContext: () => { if (AL.currentCtx !== null) { return AL.currentCtx.id; } return 0; }, alcMakeContextCurrent__proxy: 'sync', alcMakeContextCurrent: (contextId) => { if (contextId === 0) { AL.currentCtx = null; } else { AL.currentCtx = AL.contexts[contextId]; } return {{{ cDefs.ALC_TRUE }}}; }, alcGetContextsDevice__proxy: 'sync', alcGetContextsDevice: (contextId) => { if (contextId in AL.contexts) { return AL.contexts[contextId].deviceId; } return 0; }, // The spec is vague about what these are actually supposed to do, and NOP is a reasonable implementation alcProcessContext: (contextId) => {}, alcSuspendContext: (contextId) => {}, alcIsExtensionPresent__proxy: 'sync', alcIsExtensionPresent: (deviceId, pExtName) => { var name = UTF8ToString(pExtName); return AL.ALC_EXTENSIONS[name] ? 1 : 0; }, alcGetEnumValue__proxy: 'sync', alcGetEnumValue: (deviceId, pEnumName) => { // Spec says : // Using a NULL handle is legal, but only the // tokens defined by the AL core are guaranteed. if (deviceId !== 0 && !(deviceId in AL.deviceRefCounts)) { #if OPENAL_DEBUG dbg('alcGetEnumValue() called with an invalid device'); #endif // ALC_INVALID_DEVICE is not listed as a possible error state for // this function, sadly. return 0; } else if (!pEnumName) { AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return 0; } var name = UTF8ToString(pEnumName); // See alGetEnumValue(), but basically behave the same as OpenAL-Soft switch (name) { case 'ALC_NO_ERROR': return 0; case 'ALC_INVALID_DEVICE': return 0xA001; case 'ALC_INVALID_CONTEXT': return 0xA002; case 'ALC_INVALID_ENUM': return 0xA003; case 'ALC_INVALID_VALUE': return 0xA004; case 'ALC_OUT_OF_MEMORY': return 0xA005; case 'ALC_MAJOR_VERSION': return 0x1000; case 'ALC_MINOR_VERSION': return 0x1001; case 'ALC_ATTRIBUTES_SIZE': return 0x1002; case 'ALC_ALL_ATTRIBUTES': return 0x1003; case 'ALC_DEFAULT_DEVICE_SPECIFIER': return 0x1004; case 'ALC_DEVICE_SPECIFIER': return 0x1005; case 'ALC_EXTENSIONS': return 0x1006; case 'ALC_FREQUENCY': return 0x1007; case 'ALC_REFRESH': return 0x1008; case 'ALC_SYNC': return 0x1009; case 'ALC_MONO_SOURCES': return 0x1010; case 'ALC_STEREO_SOURCES': return 0x1011; case 'ALC_CAPTURE_DEVICE_SPECIFIER': return 0x310; case 'ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER': return 0x311; case 'ALC_CAPTURE_SAMPLES': return 0x312; /* Extensions */ case 'ALC_HRTF_SOFT': return 0x1992; case 'ALC_HRTF_ID_SOFT': return 0x1996; case 'ALC_DONT_CARE_SOFT': return 0x0002; case 'ALC_HRTF_STATUS_SOFT': return 0x1993; case 'ALC_NUM_HRTF_SPECIFIERS_SOFT': return 0x1994; case 'ALC_HRTF_SPECIFIER_SOFT': return 0x1995; case 'ALC_HRTF_DISABLED_SOFT': return 0x0000; case 'ALC_HRTF_ENABLED_SOFT': return 0x0001; case 'ALC_HRTF_DENIED_SOFT': return 0x0002; case 'ALC_HRTF_REQUIRED_SOFT': return 0x0003; case 'ALC_HRTF_HEADPHONES_DETECTED_SOFT': return 0x0004; case 'ALC_HRTF_UNSUPPORTED_FORMAT_SOFT': return 0x0005; default: #if OPENAL_DEBUG dbg(`No value for '${pEnumName}' is known by alcGetEnumValue()`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return {{{ cDefs.AL_NONE }}}; } }, alcGetString__proxy: 'sync', alcGetString__deps: ['$stringToNewUTF8'], alcGetString: (deviceId, param) => { if (AL.alcStringCache[param]) { return AL.alcStringCache[param]; } var ret; switch (param) { case {{{ cDefs.ALC_NO_ERROR }}}: ret = 'No Error'; break; case {{{ cDefs.ALC_INVALID_DEVICE }}}: ret = 'Invalid Device'; break; case 0xA002 /* ALC_INVALID_CONTEXT */: ret = 'Invalid Context'; break; case {{{ cDefs.ALC_INVALID_ENUM }}}: ret = 'Invalid Enum'; break; case {{{ cDefs.ALC_INVALID_VALUE }}}: ret = 'Invalid Value'; break; case 0xA005 /* ALC_OUT_OF_MEMORY */: ret = 'Out of Memory'; break; case 0x1004 /* ALC_DEFAULT_DEVICE_SPECIFIER */: if (globalThis.AudioContext || globalThis.webkitAudioContext) { ret = AL.DEVICE_NAME; } else { return 0; } break; case 0x1005 /* ALC_DEVICE_SPECIFIER */: if (globalThis.AudioContext || globalThis.webkitAudioContext) { ret = AL.DEVICE_NAME + '\0'; } else { ret = '\0'; } break; case 0x311 /* ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER */: ret = AL.CAPTURE_DEVICE_NAME; break; case 0x310 /* ALC_CAPTURE_DEVICE_SPECIFIER */: if (deviceId === 0) { ret = AL.CAPTURE_DEVICE_NAME + '\0'; } else { var c = AL.requireValidCaptureDevice(deviceId, 'alcGetString'); if (!c) { return 0; } ret = c.deviceName; } break; case 0x1006 /* ALC_EXTENSIONS */: if (!deviceId) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return 0; } ret = Object.keys(AL.ALC_EXTENSIONS).join(' ') break; default: AL.alcErr = {{{ cDefs.ALC_INVALID_ENUM }}}; return 0; } ret = stringToNewUTF8(ret); AL.alcStringCache[param] = ret; return ret; }, alcGetIntegerv__proxy: 'sync', alcGetIntegerv: (deviceId, param, size, pValues) => { if (size === 0 || !pValues) { // Ignore the query, per the spec return; } switch (param) { case 0x1000 /* ALC_MAJOR_VERSION */: {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; break; case 0x1001 /* ALC_MINOR_VERSION */: {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; break; case 0x1002 /* ALC_ATTRIBUTES_SIZE */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (!AL.currentCtx) { AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; return; } {{{ makeSetValue('pValues', '0', 'AL.currentCtx.attrs.length', 'i32') }}}; break; case 0x1003 /* ALC_ALL_ATTRIBUTES */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (!AL.currentCtx) { AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; return; } for (var i = 0; i < AL.currentCtx.attrs.length; i++) { {{{ makeSetValue('pValues', 'i*4', 'AL.currentCtx.attrs[i]', 'i32') }}}; } break; case 0x1007 /* ALC_FREQUENCY */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (!AL.currentCtx) { AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; return; } {{{ makeSetValue('pValues', '0', 'AL.currentCtx.audioCtx.sampleRate', 'i32') }}}; break; case 0x1010 /* ALC_MONO_SOURCES */: case 0x1011 /* ALC_STEREO_SOURCES */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (!AL.currentCtx) { AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; return; } {{{ makeSetValue('pValues', '0', '0x7FFFFFFF', 'i32') }}}; break; case 0x1992 /* ALC_HRTF_SOFT */: case 0x1993 /* ALC_HRTF_STATUS_SOFT */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } var hrtfStatus = 0 /* ALC_HRTF_DISABLED_SOFT */; for (var ctxId in AL.contexts) { var ctx = AL.contexts[ctxId]; if (ctx.deviceId === deviceId) { hrtfStatus = ctx.hrtf ? 1 /* ALC_HRTF_ENABLED_SOFT */ : 0 /* ALC_HRTF_DISABLED_SOFT */; } } {{{ makeSetValue('pValues', '0', 'hrtfStatus', 'i32') }}}; break; case 0x1994 /* ALC_NUM_HRTF_SPECIFIERS_SOFT */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; break; case 0x20003 /* ALC_MAX_AUXILIARY_SENDS */: if (!(deviceId in AL.deviceRefCounts)) { AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (!AL.currentCtx) { AL.alcErr = 0xA002 /* ALC_INVALID_CONTEXT */; return; } {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; case 0x312 /* ALC_CAPTURE_SAMPLES */: var c = AL.requireValidCaptureDevice(deviceId, 'alcGetIntegerv'); if (!c) { return; } var n = c.capturedFrameCount; var dstfreq = c.requestedSampleRate; var srcfreq = c.audioCtx.sampleRate; var nsamples = Math.floor(n * (dstfreq/srcfreq)); {{{ makeSetValue('pValues', '0', 'nsamples', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alcGetIntegerv() with param ${ptrToString(param)} not implemented yet`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_ENUM }}}; return; } }, emscripten_alcDevicePauseSOFT__proxy: 'sync', emscripten_alcDevicePauseSOFT__sig: 'vi', emscripten_alcDevicePauseSOFT: (deviceId) => { if (!(deviceId in AL.deviceRefCounts)) { #if OPENAL_DEBUG dbg('alcDevicePauseSOFT() called with an invalid device'); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (AL.paused) { return; } AL.paused = true; for (var ctxId in AL.contexts) { var ctx = AL.contexts[ctxId]; if (ctx.deviceId !== deviceId) { continue; } ctx.audioCtx.suspend(); clearInterval(ctx.interval); ctx.interval = null; } }, emscripten_alcDeviceResumeSOFT__proxy: 'sync', emscripten_alcDeviceResumeSOFT__sig: 'vi', emscripten_alcDeviceResumeSOFT: (deviceId) => { if (!(deviceId in AL.deviceRefCounts)) { #if OPENAL_DEBUG dbg('alcDeviceResumeSOFT() called with an invalid device'); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return; } if (!AL.paused) { return; } AL.paused = false; for (var ctxId in AL.contexts) { var ctx = AL.contexts[ctxId]; if (ctx.deviceId !== deviceId) { continue; } ctx.interval = setInterval(() => AL.scheduleContextAudio(ctx), AL.QUEUE_INTERVAL); ctx.audioCtx.resume(); } }, emscripten_alcGetStringiSOFT__proxy: 'sync', emscripten_alcGetStringiSOFT__sig: 'iiii', emscripten_alcGetStringiSOFT__deps: ['alcGetString', '$stringToNewUTF8'], emscripten_alcGetStringiSOFT: (deviceId, param, index) => { if (!(deviceId in AL.deviceRefCounts)) { #if OPENAL_DEBUG dbg('alcGetStringiSOFT() called with an invalid device'); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return 0; } if (AL.alcStringCache[param]) { return AL.alcStringCache[param]; } var ret; switch (param) { case 0x1995 /* ALC_HRTF_SPECIFIER_SOFT */: if (index === 0) { ret = 'Web Audio HRTF'; } else { #if OPENAL_DEBUG dbg(`alcGetStringiSOFT() with param ALC_HRTF_SPECIFIER_SOFT index ${index} is out of range`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_VALUE }}}; return 0; } break; default: if (index !== 0) { #if OPENAL_DEBUG dbg(`alcGetStringiSOFT() with param ${ptrToString(param)} not implemented yet`); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_ENUM }}}; return 0; } return _alcGetString(deviceId, param); } ret = stringToNewUTF8(ret); AL.alcStringCache[param] = ret; return ret; }, emscripten_alcResetDeviceSOFT__proxy: 'sync', emscripten_alcResetDeviceSOFT__sig: 'iii', emscripten_alcResetDeviceSOFT: (deviceId, pAttrList) => { if (!(deviceId in AL.deviceRefCounts)) { #if OPENAL_DEBUG dbg('alcResetDeviceSOFT() called with an invalid device'); #endif AL.alcErr = {{{ cDefs.ALC_INVALID_DEVICE }}}; return {{{ cDefs.ALC_FALSE }}}; } var hrtf = null; pAttrList >>= 2; if (pAttrList) { var attr = 0; var val = 0; while (true) { attr = HEAP32[pAttrList++]; if (attr === 0) { break; } val = HEAP32[pAttrList++]; switch (attr) { case 0x1992 /* ALC_HRTF_SOFT */: if (val === {{{ cDefs.ALC_TRUE }}}) { hrtf = true; } else if (val === {{{ cDefs.ALC_FALSE }}}) { hrtf = false; } break; } } } if (hrtf !== null) { // Apply hrtf attrib to all contexts for this device for (var ctxId in AL.contexts) { var ctx = AL.contexts[ctxId]; if (ctx.deviceId === deviceId) { ctx.hrtf = hrtf; AL.updateContextGlobal(ctx); } } } return {{{ cDefs.ALC_TRUE }}}; }, // *************************************************************************** // ** AL API // *************************************************************************** // ------------------------------------------------------- // -- AL Resources // ------------------------------------------------------- alGenBuffers__proxy: 'sync', alGenBuffers: (count, pBufferIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alGenBuffers() called without a valid context'); #endif return; } for (var i = 0; i < count; ++i) { var buf = { deviceId: AL.currentCtx.deviceId, id: AL.newId(), refCount: 0, audioBuf: null, frequency: 0, bytesPerSample: 2, channels: 1, length: 0, }; AL.deviceRefCounts[buf.deviceId]++; AL.buffers[buf.id] = buf; {{{ makeSetValue('pBufferIds', 'i*4', 'buf.id', 'i32') }}}; } }, alDeleteBuffers__proxy: 'sync', alDeleteBuffers: (count, pBufferIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alDeleteBuffers() called without a valid context'); #endif return; } for (var i = 0; i < count; ++i) { var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; /// Deleting the zero buffer is a legal NOP, so ignore it if (bufId === 0) { continue; } // Make sure the buffer index is valid. if (!AL.buffers[bufId]) { #if OPENAL_DEBUG dbg('alDeleteBuffers() called with an invalid buffer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } // Make sure the buffer is no longer in use. if (AL.buffers[bufId].refCount) { #if OPENAL_DEBUG dbg('alDeleteBuffers() called with a used buffer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; return; } } for (var i = 0; i < count; ++i) { var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; if (bufId === 0) { continue; } AL.deviceRefCounts[AL.buffers[bufId].deviceId]--; delete AL.buffers[bufId]; AL.freeIds.push(bufId); } }, alGenSources__proxy: 'sync', alGenSources: (count, pSourceIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alGenSources() called without a valid context'); #endif return; } for (var i = 0; i < count; ++i) { var gain = AL.currentCtx.audioCtx.createGain(); gain.connect(AL.currentCtx.gain); var src = { context: AL.currentCtx, id: AL.newId(), type: 0x1030 /* AL_UNDETERMINED */, state: {{{ cDefs.AL_INITIAL }}}, bufQueue: [AL.buffers[0]], audioQueue: [], looping: false, pitch: 1.0, dopplerShift: 1.0, gain, minGain: 0.0, maxGain: 1.0, panner: null, bufsProcessed: 0, bufStartTime: Number.NEGATIVE_INFINITY, bufOffset: 0.0, relative: false, refDistance: 1.0, maxDistance: 3.40282e38 /* FLT_MAX */, rolloffFactor: 1.0, position: [0.0, 0.0, 0.0], velocity: [0.0, 0.0, 0.0], direction: [0.0, 0.0, 0.0], coneOuterGain: 0.0, coneInnerAngle: 360.0, coneOuterAngle: 360.0, distanceModel: 0xd002 /* AL_INVERSE_DISTANCE_CLAMPED */, spatialize: 2 /* AL_AUTO_SOFT */, get playbackRate() { return this.pitch * this.dopplerShift; } }; AL.currentCtx.sources[src.id] = src; {{{ makeSetValue('pSourceIds', 'i*4', 'src.id', 'i32') }}}; } }, alDeleteSources__deps: ['alSourcei'], alDeleteSources__proxy: 'sync', alDeleteSources: (count, pSourceIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alDeleteSources() called without a valid context'); #endif return; } for (var i = 0; i < count; ++i) { var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; if (!AL.currentCtx.sources[srcId]) { #if OPENAL_DEBUG dbg('alDeleteSources() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } } for (var i = 0; i < count; ++i) { var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_STOPPED }}}); _alSourcei(srcId, 0x1009 /* AL_BUFFER */, 0); delete AL.currentCtx.sources[srcId]; AL.freeIds.push(srcId); } }, // ------------------------------------------------------- // --- AL Context State // ------------------------------------------------------- alGetError__proxy: 'sync', alGetError: () => { if (!AL.currentCtx) { return {{{ cDefs.AL_INVALID_OPERATION }}}; } // Reset error on get. var err = AL.currentCtx.err; AL.currentCtx.err = {{{ cDefs.AL_NO_ERROR }}}; return err; }, alIsExtensionPresent__proxy: 'sync', alIsExtensionPresent: (pExtName) => { var name = UTF8ToString(pExtName); return AL.AL_EXTENSIONS[name] ? 1 : 0; }, alGetEnumValue__proxy: 'sync', alGetEnumValue: (pEnumName) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alGetEnumValue() called without a valid context'); #endif return 0; } if (!pEnumName) { #if OPENAL_DEBUG dbg('alGetEnumValue() called with null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return {{{ cDefs.AL_NONE }}}; } var name = UTF8ToString(pEnumName); switch (name) { // Spec doesn't clearly state that alGetEnumValue() is required to // support _only_ extension tokens. // We should probably follow OpenAL-Soft's example and support all // of the names we know. // See http://repo.or.cz/openal-soft.git/blob/HEAD:/Alc/ALc.c case 'AL_BITS': return 0x2002; case 'AL_BUFFER': return 0x1009; case 'AL_BUFFERS_PROCESSED': return 0x1016; case 'AL_BUFFERS_QUEUED': return 0x1015; case 'AL_BYTE_OFFSET': return 0x1026; case 'AL_CHANNELS': return 0x2003; case 'AL_CONE_INNER_ANGLE': return 0x1001; case 'AL_CONE_OUTER_ANGLE': return 0x1002; case 'AL_CONE_OUTER_GAIN': return 0x1022; case 'AL_DIRECTION': return 0x1005; case 'AL_DISTANCE_MODEL': return 0xD000; case 'AL_DOPPLER_FACTOR': return 0xC000; case 'AL_DOPPLER_VELOCITY': return 0xC001; case 'AL_EXPONENT_DISTANCE': return 0xD005; case 'AL_EXPONENT_DISTANCE_CLAMPED': return 0xD006; case 'AL_EXTENSIONS': return 0xB004; case 'AL_FORMAT_MONO16': return 0x1101; case 'AL_FORMAT_MONO8': return 0x1100; case 'AL_FORMAT_STEREO16': return 0x1103; case 'AL_FORMAT_STEREO8': return 0x1102; case 'AL_FREQUENCY': return 0x2001; case 'AL_GAIN': return 0x100A; case 'AL_INITIAL': return 0x1011; case 'AL_INVALID': return -1; case 'AL_ILLEGAL_ENUM': // fallthrough case 'AL_INVALID_ENUM': return 0xA002; case 'AL_INVALID_NAME': return 0xA001; case 'AL_ILLEGAL_COMMAND': // fallthrough case 'AL_INVALID_OPERATION': return 0xA004; case 'AL_INVALID_VALUE': return 0xA003; case 'AL_INVERSE_DISTANCE': return 0xD001; case 'AL_INVERSE_DISTANCE_CLAMPED': return 0xD002; case 'AL_LINEAR_DISTANCE': return 0xD003; case 'AL_LINEAR_DISTANCE_CLAMPED': return 0xD004; case 'AL_LOOPING': return 0x1007; case 'AL_MAX_DISTANCE': return 0x1023; case 'AL_MAX_GAIN': return 0x100E; case 'AL_MIN_GAIN': return 0x100D; case 'AL_NONE': return 0; case 'AL_NO_ERROR': return 0; case 'AL_ORIENTATION': return 0x100F; case 'AL_OUT_OF_MEMORY': return 0xA005; case 'AL_PAUSED': return 0x1013; case 'AL_PENDING': return 0x2011; case 'AL_PITCH': return 0x1003; case 'AL_PLAYING': return 0x1012; case 'AL_POSITION': return 0x1004; case 'AL_PROCESSED': return 0x2012; case 'AL_REFERENCE_DISTANCE': return 0x1020; case 'AL_RENDERER': return 0xB003; case 'AL_ROLLOFF_FACTOR': return 0x1021; case 'AL_SAMPLE_OFFSET': return 0x1025; case 'AL_SEC_OFFSET': return 0x1024; case 'AL_SIZE': return 0x2004; case 'AL_SOURCE_RELATIVE': return 0x202; case 'AL_SOURCE_STATE': return 0x1010; case 'AL_SOURCE_TYPE': return 0x1027; case 'AL_SPEED_OF_SOUND': return 0xC003; case 'AL_STATIC': return 0x1028; case 'AL_STOPPED': return 0x1014; case 'AL_STREAMING': return 0x1029; case 'AL_UNDETERMINED': return 0x1030; case 'AL_UNUSED': return 0x2010; case 'AL_VELOCITY': return 0x1006; case 'AL_VENDOR': return 0xB001; case 'AL_VERSION': return 0xB002; /* Extensions */ case 'AL_AUTO_SOFT': return 0x0002; case 'AL_SOURCE_DISTANCE_MODEL': return 0x200; case 'AL_SOURCE_SPATIALIZE_SOFT': return 0x1214; case 'AL_LOOP_POINTS_SOFT': return 0x2015; case 'AL_BYTE_LENGTH_SOFT': return 0x2009; case 'AL_SAMPLE_LENGTH_SOFT': return 0x200A; case 'AL_SEC_LENGTH_SOFT': return 0x200B; case 'AL_FORMAT_MONO_FLOAT32': return 0x10010; case 'AL_FORMAT_STEREO_FLOAT32': return 0x10011; default: #if OPENAL_DEBUG dbg(`No value for '${name}' is known by alGetEnumValue()`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return 0; } }, alGetString__proxy: 'sync', alGetString__deps: ['$stringToNewUTF8'], alGetString: (param) => { if (AL.stringCache[param]) { return AL.stringCache[param]; } var ret; switch (param) { case {{{ cDefs.AL_NO_ERROR }}}: ret = 'No Error'; break; case {{{ cDefs.AL_INVALID_NAME }}}: ret = 'Invalid Name'; break; case {{{ cDefs.AL_INVALID_ENUM }}}: ret = 'Invalid Enum'; break; case {{{ cDefs.AL_INVALID_VALUE }}}: ret = 'Invalid Value'; break; case {{{ cDefs.AL_INVALID_OPERATION }}}: ret = 'Invalid Operation'; break; case 0xA005 /* AL_OUT_OF_MEMORY */: ret = 'Out of Memory'; break; case 0xB001 /* AL_VENDOR */: ret = 'Emscripten'; break; case 0xB002 /* AL_VERSION */: ret = '1.1'; break; case 0xB003 /* AL_RENDERER */: ret = 'WebAudio'; break; case 0xB004 /* AL_EXTENSIONS */: ret = Object.keys(AL.AL_EXTENSIONS).join(' '); break; default: if (AL.currentCtx) { AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; } else { #if OPENAL_DEBUG dbg('alGetString() called without a valid context'); #endif } return 0; } ret = stringToNewUTF8(ret); AL.stringCache[param] = ret; return ret; }, alEnable__proxy: 'sync', alEnable: (param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alEnable() called without a valid context'); #endif return; } switch (param) { case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: AL.currentCtx.sourceDistanceModel = true; AL.updateContextGlobal(AL.currentCtx); break; default: #if OPENAL_DEBUG dbg(`alEnable() with param ${ptrToString(param)} not implemented yet`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alDisable__proxy: 'sync', alDisable: (param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alDisable() called without a valid context'); #endif return; } switch (param) { case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: AL.currentCtx.sourceDistanceModel = false; AL.updateContextGlobal(AL.currentCtx); break; default: #if OPENAL_DEBUG dbg(`alDisable() with param ${ptrToString(param)} not implemented yet`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alIsEnabled__proxy: 'sync', alIsEnabled: (param) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alIsEnabled() called without a valid context'); #endif return 0; } switch (param) { case 0x200 /* AL_SOURCE_DISTANCE_MODEL */: return AL.currentCtx.sourceDistanceModel ? {{{ cDefs.AL_FALSE }}} : {{{ cDefs.AL_TRUE }}}; default: #if OPENAL_DEBUG dbg(`alIsEnabled() with param ${ptrToString(param)} not implemented yet`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return 0; } }, alGetDouble__proxy: 'sync', alGetDouble: (param) => { var val = AL.getGlobalParam('alGetDouble', param); if (val === null) { return 0.0; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: return val; default: #if OPENAL_DEBUG dbg(`alGetDouble(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return 0.0; } }, alGetDoublev__proxy: 'sync', alGetDoublev: (param, pValues) => { var val = AL.getGlobalParam('alGetDoublev', param); // Silently ignore null destinations, as per the spec for global state functions if (val === null || !pValues) { return; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: {{{ makeSetValue('pValues', '0', 'val', 'double') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetDoublev(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetFloat__proxy: 'sync', alGetFloat: (param) => { var val = AL.getGlobalParam('alGetFloat', param); if (val === null) { return 0.0; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: return val; default: #if OPENAL_DEBUG dbg(`alGetFloat(): param ${ptrToString(param)} has wrong signature`); #endif return 0.0; } }, alGetFloatv__proxy: 'sync', alGetFloatv: (param, pValues) => { var val = AL.getGlobalParam('alGetFloatv', param); // Silently ignore null destinations, as per the spec for global state functions if (val === null || !pValues) { return; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: {{{ makeSetValue('pValues', '0', 'val', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetFloatv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetInteger__proxy: 'sync', alGetInteger: (param) => { var val = AL.getGlobalParam('alGetInteger', param); if (val === null) { return 0; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: return val; default: #if OPENAL_DEBUG dbg(`alGetInteger(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return 0; } }, alGetIntegerv__proxy: 'sync', alGetIntegerv: (param, pValues) => { var val = AL.getGlobalParam('alGetIntegerv', param); // Silently ignore null destinations, as per the spec for global state functions if (val === null || !pValues) { return; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetIntegerv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetBoolean__proxy: 'sync', alGetBoolean: (param) => { var val = AL.getGlobalParam('alGetBoolean', param); if (val === null) { return {{{ cDefs.AL_FALSE }}}; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: return val !== 0 ? {{{ cDefs.AL_TRUE }}} : {{{ cDefs.AL_FALSE }}}; default: #if OPENAL_DEBUG dbg(`alGetBoolean(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return {{{ cDefs.AL_FALSE }}}; } }, alGetBooleanv__proxy: 'sync', alGetBooleanv: (param, pValues) => { var val = AL.getGlobalParam('alGetBooleanv', param); // Silently ignore null destinations, as per the spec for global state functions if (val === null || !pValues) { return; } switch (param) { case {{{ cDefs.AL_DOPPLER_FACTOR }}}: case {{{ cDefs.AL_SPEED_OF_SOUND }}}: case {{{ cDefs.AL_DISTANCE_MODEL }}}: {{{ makeSetValue('pValues', '0', 'val', 'i8') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetBooleanv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alDistanceModel__proxy: 'sync', alDistanceModel: (model) => { AL.setGlobalParam('alDistanceModel', {{{ cDefs.AL_DISTANCE_MODEL }}}, model); }, alSpeedOfSound__proxy: 'sync', alSpeedOfSound: (value) => { AL.setGlobalParam('alSpeedOfSound', {{{ cDefs.AL_SPEED_OF_SOUND }}}, value); }, alDopplerFactor__proxy: 'sync', alDopplerFactor: (value) => { AL.setGlobalParam('alDopplerFactor', {{{ cDefs.AL_DOPPLER_FACTOR }}}, value); }, // http://openal.996291.n3.nabble.com/alSpeedOfSound-or-alDopperVelocity-tp1960.html // alDopplerVelocity() sets a multiplier for the speed of sound. // It's deprecated since it's equivalent to directly calling // alSpeedOfSound() with an appropriately premultiplied value. alDopplerVelocity__proxy: 'sync', alDopplerVelocity: (value) => { warnOnce('alDopplerVelocity() is deprecated, and only kept for compatibility with OpenAL 1.0. Use alSpeedOfSound() instead.'); if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alDopplerVelocity() called without a valid context'); #endif return; } if (value <= 0) { // Negative or zero values are disallowed AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } }, // ------------------------------------------------------- // -- AL Listener State // ------------------------------------------------------- alGetListenerf__proxy: 'sync', alGetListenerf: (param, pValue) => { var val = AL.getListenerParam('alGetListenerf', param); if (val === null) { return; } if (!pValue) { #if OPENAL_DEBUG dbg('alGetListenerf() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_GAIN }}}: {{{ makeSetValue('pValue', '0', 'val', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetListenerf(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetListener3f__proxy: 'sync', alGetListener3f: (param, pValue0, pValue1, pValue2) => { var val = AL.getListenerParam('alGetListener3f', param); if (val === null) { return; } if (!pValue0 || !pValue1 || !pValue2) { #if OPENAL_DEBUG dbg('alGetListener3f() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValue0', '0', 'val[0]', 'float') }}}; {{{ makeSetValue('pValue1', '0', 'val[1]', 'float') }}}; {{{ makeSetValue('pValue2', '0', 'val[2]', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetListener3f(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetListenerfv__proxy: 'sync', alGetListenerfv: (param, pValues) => { var val = AL.getListenerParam('alGetListenerfv', param); if (val === null) { return; } if (!pValues) { #if OPENAL_DEBUG dbg('alGetListenerfv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; break; case {{{ cDefs.AL_ORIENTATION }}}: {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; {{{ makeSetValue('pValues', '12', 'val[3]', 'float') }}}; {{{ makeSetValue('pValues', '16', 'val[4]', 'float') }}}; {{{ makeSetValue('pValues', '20', 'val[5]', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetListenerfv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetListeneri__proxy: 'sync', alGetListeneri: (param, pValue) => { var val = AL.getListenerParam('alGetListeneri', param); if (val === null) { return; } if (!pValue) { #if OPENAL_DEBUG dbg('alGetListeneri() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG dbg(`alGetListeneri(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; }, alGetListener3i__proxy: 'sync', alGetListener3i: (param, pValue0, pValue1, pValue2) => { var val = AL.getListenerParam('alGetListener3i', param); if (val === null) { return; } if (!pValue0 || !pValue1 || !pValue2) { #if OPENAL_DEBUG dbg('alGetListener3i() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValue0', '0', 'val[0]', 'i32') }}}; {{{ makeSetValue('pValue1', '0', 'val[1]', 'i32') }}}; {{{ makeSetValue('pValue2', '0', 'val[2]', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetListener3i(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetListeneriv__proxy: 'sync', alGetListeneriv: (param, pValues) => { var val = AL.getListenerParam('alGetListeneriv', param); if (val === null) { return; } if (!pValues) { #if OPENAL_DEBUG dbg('alGetListeneriv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; break; case {{{ cDefs.AL_ORIENTATION }}}: {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; {{{ makeSetValue('pValues', '12', 'val[3]', 'i32') }}}; {{{ makeSetValue('pValues', '16', 'val[4]', 'i32') }}}; {{{ makeSetValue('pValues', '20', 'val[5]', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetListeneriv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alListenerf__proxy: 'sync', alListenerf: (param, value) => { switch (param) { case {{{ cDefs.AL_GAIN }}}: AL.setListenerParam('alListenerf', param, value); break; default: AL.setListenerParam('alListenerf', param, null); break; } }, alListener3f__proxy: 'sync', alListener3f: (param, value0, value1, value2) => { switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = value0; AL.paramArray[1] = value1; AL.paramArray[2] = value2; AL.setListenerParam('alListener3f', param, AL.paramArray); break; default: AL.setListenerParam('alListener3f', param, null); break; } }, alListenerfv__proxy: 'sync', alListenerfv: (param, pValues) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alListenerfv() called without a valid context'); #endif return; } if (!pValues) { #if OPENAL_DEBUG dbg('alListenerfv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'float') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'float') }}}; AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'float') }}}; AL.setListenerParam('alListenerfv', param, AL.paramArray); break; case {{{ cDefs.AL_ORIENTATION }}}: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'float') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'float') }}}; AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'float') }}}; AL.paramArray[3] = {{{ makeGetValue('pValues', '12', 'float') }}}; AL.paramArray[4] = {{{ makeGetValue('pValues', '16', 'float') }}}; AL.paramArray[5] = {{{ makeGetValue('pValues', '20', 'float') }}}; AL.setListenerParam('alListenerfv', param, AL.paramArray); break; default: AL.setListenerParam('alListenerfv', param, null); break; } }, alListeneri__proxy: 'sync', alListeneri: (param, value) => { AL.setListenerParam('alListeneri', param, null); }, alListener3i__proxy: 'sync', alListener3i: (param, value0, value1, value2) => { switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = value0; AL.paramArray[1] = value1; AL.paramArray[2] = value2; AL.setListenerParam('alListener3i', param, AL.paramArray); break; default: AL.setListenerParam('alListener3i', param, null); break; } }, alListeneriv__proxy: 'sync', alListeneriv: (param, pValues) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alListeneriv() called without a valid context'); #endif return; } if (!pValues) { #if OPENAL_DEBUG dbg('alListeneriv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'i32') }}}; AL.setListenerParam('alListeneriv', param, AL.paramArray); break; case {{{ cDefs.AL_ORIENTATION }}}: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'i32') }}}; AL.paramArray[3] = {{{ makeGetValue('pValues', '12', 'i32') }}}; AL.paramArray[4] = {{{ makeGetValue('pValues', '16', 'i32') }}}; AL.paramArray[5] = {{{ makeGetValue('pValues', '20', 'i32') }}}; AL.setListenerParam('alListeneriv', param, AL.paramArray); break; default: AL.setListenerParam('alListeneriv', param, null); break; } }, // ------------------------------------------------------- // -- AL Buffer State // ------------------------------------------------------- alIsBuffer__proxy: 'sync', alIsBuffer: (bufferId) => { if (!AL.currentCtx) { return false; } if (bufferId > AL.buffers.length) { return false; } if (!AL.buffers[bufferId]) { return false; } return true; }, alBufferData__proxy: 'sync', alBufferData: (bufferId, format, pData, size, freq) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alBufferData() called without a valid context'); #endif return; } var buf = AL.buffers[bufferId]; if (!buf) { #if OPENAL_DEBUG dbg('alBufferData() called with an invalid buffer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } if (freq <= 0) { #if OPENAL_DEBUG dbg('alBufferData() called with an invalid frequency'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } var audioBuf = null; try { switch (format) { case 0x1100 /* AL_FORMAT_MONO8 */: if (size > 0) { audioBuf = AL.currentCtx.audioCtx.createBuffer(1, size, freq); var channel0 = audioBuf.getChannelData(0); for (var i = 0; i < size; ++i) { channel0[i] = HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; } } buf.bytesPerSample = 1; buf.channels = 1; buf.length = size; break; case 0x1101 /* AL_FORMAT_MONO16 */: if (size > 0) { audioBuf = AL.currentCtx.audioCtx.createBuffer(1, size >> 1, freq); var channel0 = audioBuf.getChannelData(0); pData >>= 1; for (var i = 0; i < size >> 1; ++i) { channel0[i] = HEAP16[pData++] * 0.000030517578125 /* 1/32768 */; } } buf.bytesPerSample = 2; buf.channels = 1; buf.length = size >> 1; break; case 0x1102 /* AL_FORMAT_STEREO8 */: if (size > 0) { audioBuf = AL.currentCtx.audioCtx.createBuffer(2, size >> 1, freq); var channel0 = audioBuf.getChannelData(0); var channel1 = audioBuf.getChannelData(1); for (var i = 0; i < size >> 1; ++i) { channel0[i] = HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; channel1[i] = HEAPU8[pData++] * 0.0078125 /* 1/128 */ - 1.0; } } buf.bytesPerSample = 1; buf.channels = 2; buf.length = size >> 1; break; case 0x1103 /* AL_FORMAT_STEREO16 */: if (size > 0) { audioBuf = AL.currentCtx.audioCtx.createBuffer(2, size >> 2, freq); var channel0 = audioBuf.getChannelData(0); var channel1 = audioBuf.getChannelData(1); pData >>= 1; for (var i = 0; i < size >> 2; ++i) { channel0[i] = HEAP16[pData++] * 0.000030517578125 /* 1/32768 */; channel1[i] = HEAP16[pData++] * 0.000030517578125 /* 1/32768 */; } } buf.bytesPerSample = 2; buf.channels = 2; buf.length = size >> 2; break; case 0x10010 /* AL_FORMAT_MONO_FLOAT32 */: if (size > 0) { audioBuf = AL.currentCtx.audioCtx.createBuffer(1, size >> 2, freq); var channel0 = audioBuf.getChannelData(0); pData >>= 2; for (var i = 0; i < size >> 2; ++i) { channel0[i] = HEAPF32[pData++]; } } buf.bytesPerSample = 4; buf.channels = 1; buf.length = size >> 2; break; case 0x10011 /* AL_FORMAT_STEREO_FLOAT32 */: if (size > 0) { audioBuf = AL.currentCtx.audioCtx.createBuffer(2, size >> 3, freq); var channel0 = audioBuf.getChannelData(0); var channel1 = audioBuf.getChannelData(1); pData >>= 2; for (var i = 0; i < size >> 3; ++i) { channel0[i] = HEAPF32[pData++]; channel1[i] = HEAPF32[pData++]; } } buf.bytesPerSample = 4; buf.channels = 2; buf.length = size >> 3; break; default: #if OPENAL_DEBUG dbg(`alBufferData() called with invalid format ${format}`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } buf.frequency = freq; buf.audioBuf = audioBuf; } catch (e) { #if OPENAL_DEBUG dbg(`alBufferData() upload failed with an exception ${e}`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } }, alGetBufferf__proxy: 'sync', alGetBufferf: (bufferId, param, pValue) => { var val = AL.getBufferParam('alGetBufferf', bufferId, param); if (val === null) { return; } if (!pValue) { #if OPENAL_DEBUG dbg('alGetBufferf() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG dbg(`alGetBufferf(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; }, alGetBuffer3f__proxy: 'sync', alGetBuffer3f: (bufferId, param, pValue0, pValue1, pValue2) => { var val = AL.getBufferParam('alGetBuffer3f', bufferId, param); if (val === null) { return; } if (!pValue0 || !pValue1 || !pValue2) { #if OPENAL_DEBUG dbg('alGetBuffer3f() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG dbg(`alGetBuffer3f(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; }, alGetBufferfv__proxy: 'sync', alGetBufferfv: (bufferId, param, pValues) => { var val = AL.getBufferParam('alGetBufferfv', bufferId, param); if (val === null) { return; } if (!pValues) { #if OPENAL_DEBUG dbg('alGetBufferfv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG dbg(`alGetBufferfv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; }, alGetBufferi__proxy: 'sync', alGetBufferi: (bufferId, param, pValue) => { var val = AL.getBufferParam('alGetBufferi', bufferId, param); if (val === null) { return; } if (!pValue) { #if OPENAL_DEBUG dbg('alGetBufferi() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x2001 /* AL_FREQUENCY */: case 0x2002 /* AL_BITS */: case 0x2003 /* AL_CHANNELS */: case 0x2004 /* AL_SIZE */: {{{ makeSetValue('pValue', '0', 'val', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetBufferi(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetBuffer3i__proxy: 'sync', alGetBuffer3i: (bufferId, param, pValue0, pValue1, pValue2) => { var val = AL.getBufferParam('alGetBuffer3i', bufferId, param); if (val === null) { return; } if (!pValue0 || !pValue1 || !pValue2) { #if OPENAL_DEBUG dbg('alGetBuffer3i() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } #if OPENAL_DEBUG dbg(`alGetBuffer3i(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; }, alGetBufferiv__proxy: 'sync', alGetBufferiv: (bufferId, param, pValues) => { var val = AL.getBufferParam('alGetBufferiv', bufferId, param); if (val === null) { return; } if (!pValues) { #if OPENAL_DEBUG dbg('alGetBufferiv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x2001 /* AL_FREQUENCY */: case 0x2002 /* AL_BITS */: case 0x2003 /* AL_CHANNELS */: case 0x2004 /* AL_SIZE */: {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; break; case 0x2015 /* AL_LOOP_POINTS_SOFT */: {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetBufferiv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, // All of the remaining alBuffer* setters and getters are only of interest // to extensions which need them. Core OpenAL alone defines no valid // property for these. alBufferf__proxy: 'sync', alBufferf: (bufferId, param, value) => { AL.setBufferParam('alBufferf', bufferId, param, null); }, alBuffer3f__proxy: 'sync', alBuffer3f: (bufferId, param, value0, value1, value2) => { AL.setBufferParam('alBuffer3f', bufferId, param, null); }, alBufferfv__proxy: 'sync', alBufferfv: (bufferId, param, pValues) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alBufferfv() called without a valid context'); #endif return; } if (!pValues) { #if OPENAL_DEBUG dbg('alBufferfv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } AL.setBufferParam('alBufferfv', bufferId, param, null); }, alBufferi__proxy: 'sync', alBufferi: (bufferId, param, value) => { AL.setBufferParam('alBufferi', bufferId, param, null); }, alBuffer3i__proxy: 'sync', alBuffer3i: (bufferId, param, value0, value1, value2) => { AL.setBufferParam('alBuffer3i', bufferId, param, null); }, alBufferiv__proxy: 'sync', alBufferiv: (bufferId, param, pValues) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alBufferiv() called without a valid context'); #endif return; } if (!pValues) { #if OPENAL_DEBUG dbg('alBufferiv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x2015 /* AL_LOOP_POINTS_SOFT */: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; AL.setBufferParam('alBufferiv', bufferId, param, AL.paramArray); break; default: AL.setBufferParam('alBufferiv', bufferId, param, null); break; } }, // ------------------------------------------------------- // -- AL Source State // ------------------------------------------------------- alIsSource__proxy: 'sync', alIsSource: (sourceId) => { if (!AL.currentCtx) { return false; } if (!AL.currentCtx.sources[sourceId]) { return false; } return true; }, alSourceQueueBuffers__proxy: 'sync', alSourceQueueBuffers: (sourceId, count, pBufferIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceQueueBuffers() called without a valid context'); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourceQueueBuffers() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } if (src.type === {{{ cDefs.AL_STATIC }}}) { #if OPENAL_DEBUG dbg('alSourceQueueBuffers() called while a static buffer is bound'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; return; } if (count === 0) { return; } // Find the first non-zero buffer in the queue to determine the proper format var templateBuf = AL.buffers[0]; for (var buf of src.bufQueue) { if (buf.id !== 0) { templateBuf = buf; break; } } for (var i = 0; i < count; ++i) { var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; var buf = AL.buffers[bufId]; if (!buf) { #if OPENAL_DEBUG dbg('alSourceQueueBuffers() called with an invalid buffer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } // Check that the added buffer has the correct format. If the template is the zero buffer, any format is valid. if (templateBuf.id !== 0 && ( buf.frequency !== templateBuf.frequency || buf.bytesPerSample !== templateBuf.bytesPerSample || buf.channels !== templateBuf.channels) ) { #if OPENAL_DEBUG dbg('alSourceQueueBuffers() called with a buffer of different format'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_OPERATION }}}; } } // If the only buffer in the queue is the zero buffer, clear the queue before we add anything. if (src.bufQueue.length === 1 && src.bufQueue[0].id === 0) { src.bufQueue.length = 0; } src.type = 0x1029 /* AL_STREAMING */; for (var i = 0; i < count; ++i) { var bufId = {{{ makeGetValue('pBufferIds', 'i*4', 'i32') }}}; var buf = AL.buffers[bufId]; buf.refCount++; src.bufQueue.push(buf); } // if the source is looping, cancel the schedule so we can reschedule the loop order if (src.looping) { AL.cancelPendingSourceAudio(src); } AL.initSourcePanner(src); AL.scheduleSourceAudio(src); }, alSourceUnqueueBuffers__proxy: 'sync', alSourceUnqueueBuffers: (sourceId, count, pBufferIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceUnqueueBuffers() called without a valid context'); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourceUnqueueBuffers() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } if (count > (src.bufQueue.length === 1 && src.bufQueue[0].id === 0 ? 0 : src.bufsProcessed)) { AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } if (count === 0) { return; } for (var i = 0; i < count; i++) { var buf = src.bufQueue.shift(); buf.refCount--; // Write the buffers index out to the return list. {{{ makeSetValue('pBufferIds', 'i*4', 'buf.id', 'i32') }}}; src.bufsProcessed--; } /// If the queue is empty, put the zero buffer back in if (src.bufQueue.length === 0) { src.bufQueue.push(AL.buffers[0]); } AL.initSourcePanner(src); AL.scheduleSourceAudio(src); }, alSourcePlay__proxy: 'sync', alSourcePlay: (sourceId) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourcePlay() called without a valid context'); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourcePlay() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } AL.setSourceState(src, {{{ cDefs.AL_PLAYING }}}); }, alSourcePlayv__proxy: 'sync', alSourcePlayv: (count, pSourceIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourcePlayv() called without a valid context'); #endif return; } if (!pSourceIds) { #if OPENAL_DEBUG dbg('alSourcePlayv() called with null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; } for (var i = 0; i < count; ++i) { if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { #if OPENAL_DEBUG dbg('alSourcePlayv() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } } for (var i = 0; i < count; ++i) { var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_PLAYING }}}); } }, alSourceStop__proxy: 'sync', alSourceStop: (sourceId) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceStop() called without a valid context'); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourceStop() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); }, alSourceStopv__proxy: 'sync', alSourceStopv: (count, pSourceIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceStopv() called without a valid context'); #endif return; } if (!pSourceIds) { #if OPENAL_DEBUG dbg('alSourceStopv() called with null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; } for (var i = 0; i < count; ++i) { if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { #if OPENAL_DEBUG dbg('alSourceStopv() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } } for (var i = 0; i < count; ++i) { var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_STOPPED }}}); } }, alSourceRewind__proxy: 'sync', alSourceRewind: (sourceId) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceRewind() called without a valid context'); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourceRewind() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } // Stop the source first to clear the source queue AL.setSourceState(src, {{{ cDefs.AL_STOPPED }}}); // Now set the state of AL_INITIAL according to the specification AL.setSourceState(src, {{{ cDefs.AL_INITIAL }}}); }, alSourceRewindv__proxy: 'sync', alSourceRewindv: (count, pSourceIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceRewindv() called without a valid context'); #endif return; } if (!pSourceIds) { #if OPENAL_DEBUG dbg('alSourceRewindv() called with null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; } for (var i = 0; i < count; ++i) { if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { #if OPENAL_DEBUG dbg('alSourceRewindv() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } } for (var i = 0; i < count; ++i) { var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_INITIAL }}}); } }, alSourcePause__proxy: 'sync', alSourcePause: (sourceId) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourcePause() called without a valid context'); #endif return; } var src = AL.currentCtx.sources[sourceId]; if (!src) { #if OPENAL_DEBUG dbg('alSourcePause() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } AL.setSourceState(src, {{{ cDefs.AL_PAUSED }}}); }, alSourcePausev__proxy: 'sync', alSourcePausev: (count, pSourceIds) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourcePausev() called without a valid context'); #endif return; } if (!pSourceIds) { #if OPENAL_DEBUG dbg('alSourcePausev() called with null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; } for (var i = 0; i < count; ++i) { if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { #if OPENAL_DEBUG dbg('alSourcePausev() called with an invalid source'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_NAME }}}; return; } } for (var i = 0; i < count; ++i) { var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; AL.setSourceState(AL.currentCtx.sources[srcId], {{{ cDefs.AL_PAUSED }}}); } }, alGetSourcef__proxy: 'sync', alGetSourcef: (sourceId, param, pValue) => { var val = AL.getSourceParam('alGetSourcef', sourceId, param); if (val === null) { return; } if (!pValue) { #if OPENAL_DEBUG dbg('alGetSourcef() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1003 /* AL_PITCH */: case {{{ cDefs.AL_GAIN }}}: case 0x100D /* AL_MIN_GAIN */: case 0x100E /* AL_MAX_GAIN */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1022 /* AL_CONE_OUTER_GAIN */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x200B /* AL_SEC_LENGTH_SOFT */: {{{ makeSetValue('pValue', '0', 'val', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetSourcef(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetSource3f__proxy: 'sync', alGetSource3f: (sourceId, param, pValue0, pValue1, pValue2) => { var val = AL.getSourceParam('alGetSource3f', sourceId, param); if (val === null) { return; } if (!pValue0 || !pValue1 || !pValue2) { #if OPENAL_DEBUG dbg('alGetSource3f() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValue0', '0', 'val[0]', 'float') }}}; {{{ makeSetValue('pValue1', '0', 'val[1]', 'float') }}}; {{{ makeSetValue('pValue2', '0', 'val[2]', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetSource3f(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetSourcefv__proxy: 'sync', alGetSourcefv: (sourceId, param, pValues) => { var val = AL.getSourceParam('alGetSourcefv', sourceId, param); if (val === null) { return; } if (!pValues) { #if OPENAL_DEBUG dbg('alGetSourcefv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1003 /* AL_PITCH */: case {{{ cDefs.AL_GAIN }}}: case 0x100D /* AL_MIN_GAIN */: case 0x100E /* AL_MAX_GAIN */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1022 /* AL_CONE_OUTER_GAIN */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x200B /* AL_SEC_LENGTH_SOFT */: {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; break; case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetSourcefv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetSourcei__proxy: 'sync', alGetSourcei: (sourceId, param, pValue) => { var val = AL.getSourceParam('alGetSourcei', sourceId, param); if (val === null) { return; } if (!pValue) { #if OPENAL_DEBUG dbg('alGetSourcei() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x202 /* AL_SOURCE_RELATIVE */: case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1007 /* AL_LOOPING */: case 0x1009 /* AL_BUFFER */: case 0x1010 /* AL_SOURCE_STATE */: case 0x1015 /* AL_BUFFERS_QUEUED */: case 0x1016 /* AL_BUFFERS_PROCESSED */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x1027 /* AL_SOURCE_TYPE */: case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: case 0x2009 /* AL_BYTE_LENGTH_SOFT */: case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: case {{{ cDefs.AL_DISTANCE_MODEL }}}: {{{ makeSetValue('pValue', '0', 'val', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetSourcei(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetSource3i__proxy: 'sync', alGetSource3i: (sourceId, param, pValue0, pValue1, pValue2) => { var val = AL.getSourceParam('alGetSource3i', sourceId, param); if (val === null) { return; } if (!pValue0 || !pValue1 || !pValue2) { #if OPENAL_DEBUG dbg('alGetSource3i() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValue0', '0', 'val[0]', 'i32') }}}; {{{ makeSetValue('pValue1', '0', 'val[1]', 'i32') }}}; {{{ makeSetValue('pValue2', '0', 'val[2]', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetSource3i(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alGetSourceiv__proxy: 'sync', alGetSourceiv: (sourceId, param, pValues) => { var val = AL.getSourceParam('alGetSourceiv', sourceId, param); if (val === null) { return; } if (!pValues) { #if OPENAL_DEBUG dbg('alGetSourceiv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x202 /* AL_SOURCE_RELATIVE */: case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1007 /* AL_LOOPING */: case 0x1009 /* AL_BUFFER */: case 0x1010 /* AL_SOURCE_STATE */: case 0x1015 /* AL_BUFFERS_QUEUED */: case 0x1016 /* AL_BUFFERS_PROCESSED */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x1027 /* AL_SOURCE_TYPE */: case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: case 0x2009 /* AL_BYTE_LENGTH_SOFT */: case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: case {{{ cDefs.AL_DISTANCE_MODEL }}}: {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; break; case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; break; default: #if OPENAL_DEBUG dbg(`alGetSourceiv(): param ${ptrToString(param)} has wrong signature`); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_ENUM }}}; return; } }, alSourcef__proxy: 'sync', alSourcef: (sourceId, param, value) => { switch (param) { case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1003 /* AL_PITCH */: case {{{ cDefs.AL_GAIN }}}: case 0x100D /* AL_MIN_GAIN */: case 0x100E /* AL_MAX_GAIN */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1022 /* AL_CONE_OUTER_GAIN */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x200B /* AL_SEC_LENGTH_SOFT */: AL.setSourceParam('alSourcef', sourceId, param, value); break; default: AL.setSourceParam('alSourcef', sourceId, param, null); break; } }, alSource3f__proxy: 'sync', alSource3f: (sourceId, param, value0, value1, value2) => { switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = value0; AL.paramArray[1] = value1; AL.paramArray[2] = value2; AL.setSourceParam('alSource3f', sourceId, param, AL.paramArray); break; default: AL.setSourceParam('alSource3f', sourceId, param, null); break; } }, alSourcefv__proxy: 'sync', alSourcefv: (sourceId, param, pValues) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourcefv() called without a valid context'); #endif return; } if (!pValues) { #if OPENAL_DEBUG dbg('alSourcefv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1003 /* AL_PITCH */: case {{{ cDefs.AL_GAIN }}}: case 0x100D /* AL_MIN_GAIN */: case 0x100E /* AL_MAX_GAIN */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1022 /* AL_CONE_OUTER_GAIN */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x200B /* AL_SEC_LENGTH_SOFT */: var val = {{{ makeGetValue('pValues', '0', 'float') }}}; AL.setSourceParam('alSourcefv', sourceId, param, val); break; case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'float') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'float') }}}; AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'float') }}}; AL.setSourceParam('alSourcefv', sourceId, param, AL.paramArray); break; default: AL.setSourceParam('alSourcefv', sourceId, param, null); break; } }, alSourcei__proxy: 'sync', alSourcei: (sourceId, param, value) => { switch (param) { case 0x202 /* AL_SOURCE_RELATIVE */: case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1007 /* AL_LOOPING */: case 0x1009 /* AL_BUFFER */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: case 0x2009 /* AL_BYTE_LENGTH_SOFT */: case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: case {{{ cDefs.AL_DISTANCE_MODEL }}}: AL.setSourceParam('alSourcei', sourceId, param, value); break; default: AL.setSourceParam('alSourcei', sourceId, param, null); break; } }, alSource3i__proxy: 'sync', alSource3i: (sourceId, param, value0, value1, value2) => { switch (param) { case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = value0; AL.paramArray[1] = value1; AL.paramArray[2] = value2; AL.setSourceParam('alSource3i', sourceId, param, AL.paramArray); break; default: AL.setSourceParam('alSource3i', sourceId, param, null); break; } }, alSourceiv__proxy: 'sync', alSourceiv: (sourceId, param, pValues) => { if (!AL.currentCtx) { #if OPENAL_DEBUG dbg('alSourceiv() called without a valid context'); #endif return; } if (!pValues) { #if OPENAL_DEBUG dbg('alSourceiv() called with a null pointer'); #endif AL.currentCtx.err = {{{ cDefs.AL_INVALID_VALUE }}}; return; } switch (param) { case 0x202 /* AL_SOURCE_RELATIVE */: case 0x1001 /* AL_CONE_INNER_ANGLE */: case 0x1002 /* AL_CONE_OUTER_ANGLE */: case 0x1007 /* AL_LOOPING */: case 0x1009 /* AL_BUFFER */: case 0x1020 /* AL_REFERENCE_DISTANCE */: case 0x1021 /* AL_ROLLOFF_FACTOR */: case 0x1023 /* AL_MAX_DISTANCE */: case 0x1024 /* AL_SEC_OFFSET */: case 0x1025 /* AL_SAMPLE_OFFSET */: case 0x1026 /* AL_BYTE_OFFSET */: case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: case 0x2009 /* AL_BYTE_LENGTH_SOFT */: case 0x200A /* AL_SAMPLE_LENGTH_SOFT */: case {{{ cDefs.AL_DISTANCE_MODEL }}}: var val = {{{ makeGetValue('pValues', '0', 'i32') }}}; AL.setSourceParam('alSourceiv', sourceId, param, val); break; case {{{ cDefs.AL_POSITION }}}: case {{{ cDefs.AL_DIRECTION }}}: case {{{ cDefs.AL_VELOCITY }}}: AL.paramArray[0] = {{{ makeGetValue('pValues', '0', 'i32') }}}; AL.paramArray[1] = {{{ makeGetValue('pValues', '4', 'i32') }}}; AL.paramArray[2] = {{{ makeGetValue('pValues', '8', 'i32') }}}; AL.setSourceParam('alSourceiv', sourceId, param, AL.paramArray); break; default: AL.setSourceParam('alSourceiv', sourceId, param, null); break; } } }; autoAddDeps(LibraryOpenAL, '$AL'); addToLibrary(LibraryOpenAL);