| // Copyright 2013 The Emscripten Authors. All rights reserved. |
| // Emscripten is available under two separate licenses, the MIT license and the |
| // University of Illinois/NCSA Open Source License. Both these licenses can be |
| // found in the LICENSE file. |
| |
| //'use strict'; |
| |
| var LibraryOpenAL = { |
| // ************************************************************************ |
| // ** INTERNALS |
| // ************************************************************************ |
| |
| $AL__deps: ['$Browser'], |
| $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_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 === 0 /* ALC_NO_ERROR */ || val === 0 /* 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: function() { |
| return AL.freeIds.length > 0 ? AL.freeIds.pop() : AL._nextId++; |
| }, |
| freeIds: [], |
| |
| // ------------------------------------------------------ |
| // -- Mixing Logic |
| // ------------------------------------------------------ |
| |
| scheduleContextAudio: function(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 (Browser.mainLoop.timingMode === 1 /* 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 taversed 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: function(src, lookahead) { |
| // See comment on scheduleContextAudio above. |
| if (Browser.mainLoop.timingMode === 1 /*EM_TIMING_RAF*/ && document['visibilityState'] != 'visible') { |
| return; |
| } |
| if (src.state !== 0x1012 /* 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 === 0x1028 /* 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?'); |
| } |
| |
| console.log('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: function(src) { |
| var currentTime = src.context.audioCtx.currentTime; |
| if (src.state !== 0x1012 /* 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, 0x1014 /* AL_STOPPED */); |
| } else if (src.type === 0x1028 /* 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 !== 0x1028 /* 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, 0x1014 /* 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: function(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: function(src) { |
| for (var i = 0; i < src.audioQueue.length; i++) { |
| src.audioQueue[i].stop(); |
| } |
| src.audioQueue.length = 0; |
| }, |
| |
| setSourceState: function(src, state) { |
| if (state === 0x1012 /* AL_PLAYING */) { |
| if (src.state === 0x1012 /* AL_PLAYING */ || src.state == 0x1014 /* AL_STOPPED */) { |
| src.bufsProcessed = 0; |
| src.bufOffset = 0.0; |
| #if OPENAL_DEBUG |
| console.log('setSourceState() resetting and playing source ' + src.id); |
| #endif |
| } else { |
| #if OPENAL_DEBUG |
| console.log('setSourceState() playing source ' + src.id + ' at ' + src.bufOffset); |
| #endif |
| } |
| |
| AL.stopSourceAudio(src); |
| |
| src.state = 0x1012 /* AL_PLAYING */; |
| src.bufStartTime = Number.NEGATIVE_INFINITY; |
| AL.scheduleSourceAudio(src); |
| } else if (state === 0x1013 /* AL_PAUSED */) { |
| if (src.state === 0x1012 /* AL_PLAYING */) { |
| // Store off the current offset to restore with on resume. |
| AL.updateSourceTime(src); |
| AL.stopSourceAudio(src); |
| |
| src.state = 0x1013 /* AL_PAUSED */; |
| #if OPENAL_DEBUG |
| console.log('setSourceState() pausing source ' + src.id + ' at ' + src.bufOffset); |
| #endif |
| } |
| } else if (state === 0x1014 /* AL_STOPPED */) { |
| if (src.state !== 0x1011 /* AL_INITIAL */) { |
| src.state = 0x1014 /* AL_STOPPED */; |
| src.bufsProcessed = src.bufQueue.length; |
| src.bufStartTime = Number.NEGATIVE_INFINITY; |
| src.bufOffset = 0.0; |
| AL.stopSourceAudio(src); |
| #if OPENAL_DEBUG |
| console.log('setSourceState() stopping source ' + src.id); |
| #endif |
| } |
| } else if (state === 0x1011 /* AL_INITIAL */) { |
| if (src.state !== 0x1011 /* AL_INITIAL */) { |
| src.state = 0x1011 /* AL_INITIAL */; |
| src.bufsProcessed = 0; |
| src.bufStartTime = Number.NEGATIVE_INFINITY; |
| src.bufOffset = 0.0; |
| AL.stopSourceAudio(src); |
| #if OPENAL_DEBUG |
| console.log('setSourceState() initializing source ' + src.id); |
| #endif |
| } |
| } |
| }, |
| |
| initSourcePanner: function(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 === 1 /* 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: function(ctx) { |
| for (var i in ctx.sources) { |
| AL.updateSourceGlobal(ctx.sources[i]); |
| } |
| }, |
| |
| updateSourceGlobal: function(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 0 /* 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: function(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: function(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 coordinate 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 = function(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) { |
| panner.positionX.value = posX; |
| panner.positionY.value = posY; |
| 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) { |
| panner.orientationX.value = dirX; |
| panner.orientationY.value = dirY; |
| 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: function(src) { |
| if (src.state === 0x1012 /* 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 === 0x1028 /* 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: function(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: function(src) { |
| AL.updateSourceTime(src); |
| |
| var offset = 0.0; |
| for (var i = 0; i < src.bufsProcessed; i++) { |
| offset += src.bufQueue[i].audioBuf.duration; |
| } |
| offset += src.bufOffset; |
| |
| return offset; |
| }, |
| |
| sourceSeek: function(src, offset) { |
| var playing = src.state == 0x1012 /* AL_PLAYING */; |
| if (playing) { |
| AL.setSourceState(src, 0x1011 /* 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, 0x1012 /* AL_PLAYING */); |
| } |
| }, |
| |
| // ------------------------------------------------------ |
| // -- Accessor Helpers |
| // ------------------------------------------------------ |
| |
| getGlobalParam: function(funcname, param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return null; |
| } |
| |
| switch (param) { |
| case 0xC000 /* AL_DOPPLER_FACTOR */: |
| return AL.currentCtx.dopplerFactor; |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| return AL.currentCtx.speedOfSound; |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| return AL.currentCtx.distanceModel; |
| default: |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return null; |
| } |
| }, |
| |
| setGlobalParam: function(funcname, param, value) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return; |
| } |
| |
| switch (param) { |
| case 0xC000 /* AL_DOPPLER_FACTOR */: |
| if (!Number.isFinite(value) || value < 0.0) { // Strictly negative values are disallowed |
| #if OPENAL_DEBUG |
| console.error(funcname + '() value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| AL.currentCtx.dopplerFactor = value; |
| AL.updateListenerSpace(AL.currentCtx); |
| break; |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| if (!Number.isFinite(value) || value <= 0.0) { // Negative or zero values are disallowed |
| #if OPENAL_DEBUG |
| console.error(funcname + '() value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| AL.currentCtx.speedOfSound = value; |
| AL.updateListenerSpace(AL.currentCtx); |
| break; |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| switch (value) { |
| case 0 /* 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 |
| console.error(funcname + '() value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| getListenerParam: function(funcname, param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return null; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| return AL.currentCtx.listener.position; |
| case 0x1006 /* AL_VELOCITY */: |
| return AL.currentCtx.listener.velocity; |
| case 0x100F /* AL_ORIENTATION */: |
| return AL.currentCtx.listener.direction.concat(AL.currentCtx.listener.up); |
| case 0x100A /* AL_GAIN */: |
| return AL.currentCtx.gain.gain.value; |
| default: |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return null; |
| } |
| }, |
| |
| setListenerParam: function(funcname, param, value) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return; |
| } |
| if (value === null) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| |
| var listener = AL.currentCtx.listener; |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_POSITION value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 0x1006 /* AL_VELOCITY */: |
| if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_VELOCITY value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 0x100A /* AL_GAIN */: |
| if (!Number.isFinite(value) || value < 0.0) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_GAIN value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| AL.currentCtx.gain.gain.value = value; |
| break; |
| case 0x100F /* 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 |
| console.error(funcname + '() param AL_ORIENTATION value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| getBufferParam: function(funcname, bufferId, param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return; |
| } |
| var buf = AL.buffers[bufferId]; |
| if (!buf || bufferId === 0) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called with an invalid buffer'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* 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]; |
| } else { |
| return [ |
| (buf.audioBuf._loopStart || 0.0) * buf.frequency, |
| (buf.audioBuf._loopEnd || buf.length) * buf.frequency |
| ]; |
| } |
| default: |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return null; |
| } |
| }, |
| |
| setBufferParam: function(funcname, bufferId, param, value) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return; |
| } |
| var buf = AL.buffers[bufferId]; |
| if (!buf || bufferId === 0) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called with an invalid buffer'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| if (value === null) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x2004 /* AL_SIZE */: |
| if (value !== 0) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_SIZE value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_LOOP_POINTS_SOFT value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| if (buf.refCount > 0) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_LOOP_POINTS_SOFT set on bound buffer'); |
| #endif |
| AL.currentCtx.err = 0xA004 /* 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 |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| getSourceParam: function(funcname, sourceId, param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return null; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* 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 0x1004 /* AL_POSITION */: |
| return src.position; |
| case 0x1005 /* AL_DIRECTION */: |
| return src.direction; |
| case 0x1006 /* AL_VELOCITY */: |
| return src.velocity; |
| case 0x1007 /* AL_LOOPING */: |
| return src.looping; |
| case 0x1009 /* AL_BUFFER */: |
| if (src.type === 0x1028 /* AL_STATIC */) { |
| return src.bufQueue[0].id; |
| } else { |
| return 0; |
| } |
| case 0x100A /* 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; |
| } else { |
| return src.bufQueue.length; |
| } |
| case 0x1016 /* AL_BUFFERS_PROCESSED */: |
| if ((src.bufQueue.length === 1 && src.bufQueue[0].id === 0) || src.looping) { |
| return 0; |
| } else { |
| 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 0xD000 /* AL_DISTANCE_MODEL */: |
| return src.distanceModel; |
| default: |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return null; |
| } |
| }, |
| |
| setSourceParam: function(funcname, sourceId, param, value) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourcef() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| if (value === null) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x202 /* AL_SOURCE_RELATIVE */: |
| if (value === 1 /* AL_TRUE */) { |
| src.relative = true; |
| AL.updateSourceSpace(src); |
| } else if (value === 0 /* AL_FALSE */) { |
| src.relative = false; |
| AL.updateSourceSpace(src); |
| } else { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_SOURCE_RELATIVE value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| break; |
| case 0x1001 /* AL_CONE_INNER_ANGLE */: |
| if (!Number.isFinite(value)) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_CONE_INNER_ANGLE value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_CONE_OUTER_ANGLE value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_PITCH value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| if (src.pitch === value) { |
| break; |
| } |
| |
| src.pitch = value; |
| AL.updateSourceRate(src); |
| break; |
| case 0x1004 /* AL_POSITION */: |
| if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_POSITION value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| src.position[0] = value[0]; |
| src.position[1] = value[1]; |
| src.position[2] = value[2]; |
| AL.updateSourceSpace(src); |
| break; |
| case 0x1005 /* AL_DIRECTION */: |
| if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_DIRECTION value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| src.direction[0] = value[0]; |
| src.direction[1] = value[1]; |
| src.direction[2] = value[2]; |
| AL.updateSourceSpace(src); |
| break; |
| case 0x1006 /* AL_VELOCITY */: |
| if (!Number.isFinite(value[0]) || !Number.isFinite(value[1]) || !Number.isFinite(value[2])) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_VELOCITY value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 === 1 /* AL_TRUE */) { |
| src.looping = true; |
| AL.updateSourceTime(src); |
| if (src.type === 0x1028 /* AL_STATIC */ && src.audioQueue.length > 0) { |
| var audioSrc = src.audioQueue[0]; |
| audioSrc.loop = true; |
| audioSrc._duration = Number.POSITIVE_INFINITY; |
| } |
| } else if (value === 0 /* AL_FALSE */) { |
| src.looping = false; |
| var currentTime = AL.updateSourceTime(src); |
| if (src.type === 0x1028 /* 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 |
| console.error(funcname + '() param AL_LOOPING value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| break; |
| case 0x1009 /* AL_BUFFER */: |
| if (src.state === 0x1012 /* AL_PLAYING */ || src.state === 0x1013 /* AL_PAUSED */) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '(AL_BUFFER) called while source is playing or paused'); |
| #endif |
| AL.currentCtx.err = 0xA004 /* 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 |
| console.error('alSourcei(AL_BUFFER) called with an invalid buffer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 = 0x1028 /* AL_STATIC */; |
| } |
| |
| AL.initSourcePanner(src); |
| AL.scheduleSourceAudio(src); |
| break; |
| case 0x100A /* AL_GAIN */: |
| if (!Number.isFinite(value) || value < 0.0) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_GAIN value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_MIN_GAIN value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_MAX_GAIN value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_REFERENCE_DISTANCE value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_ROLLOFF_FACTOR value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_CORE_OUTER_GAIN value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_MAX_DISTANCE value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_SEC_OFFSET value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_SAMPLE_OFFSET value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_BYTE_OFFSET value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| AL.sourceSeek(src, value); |
| break; |
| case 0x1214 /* AL_SOURCE_SPATIALIZE_SOFT */: |
| if (value !== 0 /* AL_FALSE */ && value !== 1 /* AL_TRUE */ && value !== 2 /* AL_AUTO_SOFT */) { |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param AL_SOURCE_SPATIALIZE_SOFT value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error(funcname + '() param AL_*_LENGTH_SOFT is read only'); |
| #endif |
| AL.currentCtx.err = 0xA004 /* AL_INVALID_OPERATION */; |
| break; |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| switch (value) { |
| case 0 /* 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 |
| console.error(funcname + '() param AL_DISTANCE_MODEL value ' + value + ' is out of range'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error(funcname + '() param 0x' + param.toString(16) + ' is unknown or not implemented'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* 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 <invalid> separately because careless |
| // people might assume that most alcCapture functions |
| // accept NULL as a 'use the default' device. |
| requireValidCaptureDevice: function(deviceId, funcname) { |
| if (deviceId === 0) { |
| #if OPENAL_DEBUG |
| console.error(funcname+'() on a NULL device is an error'); |
| #endif |
| AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */; |
| return null; |
| } |
| var c = AL.captures[deviceId]; |
| if (!c) { |
| #if OPENAL_DEBUG |
| console.error(funcname+'() on an invalid device'); |
| #endif |
| AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */; |
| return null; |
| } |
| var err = c.mediaStreamError; |
| if (err) { |
| #if OPENAL_DEBUG |
| switch(err.name) { |
| case 'PermissionDeniedError': |
| console.error(funcname+'() but the user denied access to the device'); |
| break; |
| case 'NotFoundError': |
| console.error(funcname+'() but no capture device was found'); |
| break; |
| default: |
| console.error(funcname+'() but a MediaStreamError was encountered: ' + err); |
| break; |
| } |
| #endif |
| AL.alcErr = 0xA001 /* 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__proxy: 'sync', |
| alcCaptureOpenDevice__sig: 'iiiii', |
| alcCaptureOpenDevice: function(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 |
| console.error('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 |
| console.error('alcCaptureOpenDevice() with negative bufferSize'); |
| #endif |
| AL.alcErr = 0xA004 /* 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 |
| console.error('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 |
| console.error('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; |
| } |
| } |
| |
| 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 |
| console.error('alcCaptureOpenDevice() with unsupported format ' + format); |
| #endif |
| AL.alcErr = 0xA004 /* 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 |
| console.error('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: requestedSampleRate, |
| requestedSampleType: requestedSampleType, |
| outputChannelCount: 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, |
| // Either one, or none of the below two, is active. |
| mergerNode: null, |
| splitterNode: null, |
| scriptProcessorNode: null, |
| isCapturing: false, |
| buffers: buffers, |
| get bufferFrameCapacity() { |
| return buffers[0].length; |
| }, |
| capturePlayhead: 0, // current write position, in sample frames |
| capturedFrameCount: 0 |
| }; |
| |
| // Preparing for getUserMedia() |
| |
| var onError = function(mediaStreamError) { |
| newCapture.mediaStreamError = mediaStreamError; |
| #if OPENAL_DEBUG |
| console.error('navigator.getUserMedia() errored with: ' + mediaStreamError); |
| #endif |
| }; |
| var onSuccess = function(mediaStream) { |
| newCapture.mediaStreamSourceNode = newCapture.audioCtx.createMediaStreamSource(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) { |
| console.warn('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 = function(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 && 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__sig: 'ii', |
| alcCaptureCloseDevice: function(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 |
| if (c.mediaStreamSourceNode) c.mediaStreamSourceNode.disconnect(); |
| if (c.mergerNode) c.mergerNode.disconnect(); |
| if (c.splitterNode) c.splitterNode.disconnect(); |
| // May happen if user hasn't decided to grant or deny input |
| if (c.scriptProcessorNode) c.scriptProcessorNode.disconnect(); |
| |
| delete c.buffers; |
| |
| c.capturedFrameCount = 0; |
| c.isCapturing = false; |
| |
| return true; |
| }, |
| |
| alcCaptureStart__proxy: 'sync', |
| alcCaptureStart__sig: 'vi', |
| alcCaptureStart: function(deviceId) { |
| var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStart'); |
| if (!c) return; |
| |
| if (c.isCapturing) { |
| #if OPENAL_DEBUG |
| console.warn('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__sig: 'vi', |
| alcCaptureStop: function(deviceId) { |
| var c = AL.requireValidCaptureDevice(deviceId, 'alcCaptureStop'); |
| if (!c) return; |
| |
| #if OPENAL_DEBUG |
| if (!c.isCapturing) { |
| console.warn('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__sig: 'viii', |
| alcCaptureSamples: function(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. |
| if (requestedFrameCount < 0 |
| || requestedFrameCount > c.capturedFrameCount) |
| { |
| // if OPENAL_DEBUG |
| console.error('alcCaptureSamples() with invalid bufferSize'); |
| // endif |
| AL.alcErr = 0xA004 /* 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 |
| console.error('Internal error: Unknown sample type \''+c.requestedSampleType+'\''); |
| #endif |
| return; |
| } |
| |
| var dstfreq = c.requestedSampleRate; |
| var srcfreq = c.audioCtx.sampleRate; |
| |
| if (srcfreq == dstfreq) { |
| for (var i = 0, frame_i = 0; frame_i < requestedFrameCount; ++frame_i) { |
| for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { |
| var src_i = (frame_i + c.capturePlayhead) % c.capturedFrameCount; |
| setSample(i, c.buffers[chan][src_i]); |
| } |
| } |
| } 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. |
| |
| var lerp = function(from, to, progress) { |
| return (1 - progress) * from + progress * to; |
| }; |
| |
| for (var i = 0, frame_i = 0; frame_i < requestedFrameCount; ++frame_i) { |
| |
| var t = frame_i / dstfreq; // Most exact time for the current output sample |
| var src_i = (Math.floor(t*srcfreq) + c.capturePlayhead) % c.capturedFrameCount; |
| var src_next_i = (src_i+1) % c.capturedFrameCount; |
| var between = t*srcfreq - src_i; //(t - src_i/srcfreq) / ((src_i+1)/srcfreq - src_i/srcfreq); |
| |
| for (var chan = 0; chan < c.buffers.length; ++chan, ++i) { |
| var cb = c.buffers[chan]; |
| var sample = lerp(cb[src_i], cb[src_next_i], between); |
| setSample(i, sample); |
| } |
| } |
| } |
| |
| // 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__sig: 'ii', |
| alcOpenDevice: function(pDeviceName) { |
| if (pDeviceName) { |
| var name = UTF8ToString(pDeviceName); |
| if (name !== AL.DEVICE_NAME) { |
| return 0; |
| } |
| } |
| |
| if (typeof(AudioContext) !== 'undefined' || typeof(webkitAudioContext) !== 'undefined') { |
| var deviceId = AL.newId(); |
| AL.deviceRefCounts[deviceId] = 0; |
| return deviceId; |
| } else { |
| return 0; |
| } |
| }, |
| |
| alcCloseDevice__proxy: 'sync', |
| alcCloseDevice__sig: 'ii', |
| alcCloseDevice: function(deviceId) { |
| if (!(deviceId in AL.deviceRefCounts) || AL.deviceRefCounts[deviceId] > 0) { |
| return 0 /* ALC_FALSE */; |
| } |
| |
| delete AL.deviceRefCounts[deviceId]; |
| AL.freeIds.push(deviceId); |
| return 1 /* ALC_TRUE */; |
| }, |
| |
| alcCreateContext__proxy: 'sync', |
| alcCreateContext__sig: 'iii', |
| alcCreateContext: function(deviceId, pAttrList) { |
| if (!(deviceId in AL.deviceRefCounts)) { |
| #if OPENAL_DEBUG |
| console.log('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 0 /* ALC_FALSE */: |
| hrtf = false; |
| break; |
| case 1 /* ALC_TRUE */: |
| hrtf = true; |
| break; |
| case 2 /* ALC_DONT_CARE_SOFT */: |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.log('Unsupported ALC_HRTF_SOFT mode ' + val); |
| #endif |
| AL.alcErr = 0xA004 /* ALC_INVALID_VALUE */; |
| return 0; |
| } |
| break; |
| case 0x1996 /* ALC_HRTF_ID_SOFT */: |
| if (val !== 0) { |
| #if OPENAL_DEBUG |
| console.log('Invalid ALC_HRTF_ID_SOFT index ' + val); |
| #endif |
| AL.alcErr = 0xA004 /* ALC_INVALID_VALUE */; |
| return 0; |
| } |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.log('Unsupported context attribute 0x' + attr.toString(16)); |
| #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 |
| console.log('Invalid or unsupported options'); |
| #endif |
| AL.alcErr = 0xA004; /* ALC_INVALID_VALUE */ |
| } else { |
| AL.alcErr = 0xA001; /* ALC_INVALID_DEVICE */ |
| } |
| |
| return 0; |
| } |
| |
| // 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: deviceId, |
| id: AL.newId(), |
| attrs: 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(function() { AL.scheduleContextAudio(ctx); }, AL.QUEUE_INTERVAL), |
| gain: 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 === 0 /* AL_NO_ERROR */ || val === 0 /* 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__sig: 'vi', |
| alcDestroyContext: function(contextId) { |
| var ctx = AL.contexts[contextId]; |
| if (AL.currentCtx === ctx) { |
| #if OPENAL_DEBUG |
| console.log('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__sig: 'ii', |
| alcGetError: function(deviceId) { |
| var err = AL.alcErr; |
| AL.alcErr = 0 /* ALC_NO_ERROR */; |
| return err; |
| }, |
| |
| alcGetCurrentContext__proxy: 'sync', |
| alcGetCurrentContext__sig: 'i', |
| alcGetCurrentContext: function() { |
| if (AL.currentCtx !== null) { |
| return AL.currentCtx.id; |
| } else { |
| return 0; |
| } |
| }, |
| |
| alcMakeContextCurrent__proxy: 'sync', |
| alcMakeContextCurrent__sig: 'ii', |
| alcMakeContextCurrent: function(contextId) { |
| if (contextId === 0) { |
| AL.currentCtx = null; |
| return 0; |
| } else { |
| AL.currentCtx = AL.contexts[contextId]; |
| return 1; |
| } |
| }, |
| |
| alcGetContextsDevice__proxy: 'sync', |
| alcGetContextsDevice__sig: 'ii', |
| alcGetContextsDevice: function(contextId) { |
| if (contextId in AL.contexts) { |
| return AL.contexts[contextId].deviceId; |
| } else { |
| return 0; |
| } |
| }, |
| |
| // The spec is vague about what these are actually supposed to do, and NOP is a reasonable implementation |
| alcProcessContext: function(contextId) {}, |
| alcSuspendContext: function(contextId) {}, |
| |
| alcIsExtensionPresent__proxy: 'sync', |
| alcIsExtensionPresent__sig: 'iii', |
| alcIsExtensionPresent: function(deviceId, pExtName) { |
| var name = UTF8ToString(pExtName); |
| |
| return AL.ALC_EXTENSIONS[name] ? 1 : 0; |
| }, |
| |
| alcGetProcAddress__deps: ['emscripten_GetAlcProcAddress'], |
| alcGetProcAddress__proxy: 'sync', |
| alcGetProcAddress__sig: 'iii', |
| alcGetProcAddress: function(deviceId, pProcName) { |
| if (!pProcName) { |
| #if OPENAL_DEBUG |
| console.error('alcGetProcAddress() called with null name pointer'); |
| #endif |
| AL.alcErr = 0xA004 /* ALC_INVALID_VALUE */; |
| return 0; /* ALC_NONE */ |
| } |
| return _emscripten_GetAlcProcAddress(pProcName); |
| }, |
| |
| alcGetEnumValue__proxy: 'sync', |
| alcGetEnumValue__sig: 'iii', |
| alcGetEnumValue: function(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 |
| console.error('alcGetEnumValue() called with an invalid device'); |
| #endif |
| // ALC_INVALID_DEVICE is not listed as a possible error state for |
| // this function, sadly. |
| return 0 /* ALC_NONE */; |
| } else if (!pEnumName) { |
| AL.alcErr = 0xA004 /* ALC_INVALID_VALUE */; |
| return 0; /* ALC_NONE */ |
| } |
| 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 |
| console.error('No value for `' + pEnumName + '` is known by alcGetEnumValue()'); |
| #endif |
| AL.alcErr = 0xA004 /* ALC_INVALID_VALUE */; |
| return 0 /* AL_NONE */; |
| } |
| }, |
| |
| alcGetString__proxy: 'sync', |
| alcGetString__sig: 'iii', |
| alcGetString: function(deviceId, param) { |
| if (AL.alcStringCache[param]) { |
| return AL.alcStringCache[param]; |
| } |
| |
| var ret; |
| switch (param) { |
| case 0 /* ALC_NO_ERROR */: |
| ret = 'No Error'; |
| break; |
| case 0xA001 /* ALC_INVALID_DEVICE */: |
| ret = 'Invalid Device'; |
| break; |
| case 0xA002 /* ALC_INVALID_CONTEXT */: |
| ret = 'Invalid Context'; |
| break; |
| case 0xA003 /* ALC_INVALID_ENUM */: |
| ret = 'Invalid Enum'; |
| break; |
| case 0xA004 /* 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 (typeof(AudioContext) !== 'undefined' || |
| typeof(webkitAudioContext) !== 'undefined') { |
| ret = AL.DEVICE_NAME; |
| } else { |
| return 0; |
| } |
| break; |
| case 0x1005 /* ALC_DEVICE_SPECIFIER */: |
| if (typeof(AudioContext) !== 'undefined' || |
| typeof(webkitAudioContext) !== 'undefined') { |
| ret = AL.DEVICE_NAME.concat('\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.concat('\0'); |
| else { |
| var c = AL.requireValidCaptureDevice(deviceId, 'alcGetString'); |
| if (!c) { |
| return 0; |
| } |
| ret = c.deviceName; |
| } |
| break; |
| case 0x1006 /* ALC_EXTENSIONS */: |
| if (!deviceId) { |
| AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */; |
| return 0; |
| } |
| |
| ret = ''; |
| for (var ext in AL.ALC_EXTENSIONS) { |
| ret = ret.concat(ext); |
| ret = ret.concat(' '); |
| } |
| ret = ret.trim(); |
| break; |
| default: |
| AL.alcErr = 0xA003 /* ALC_INVALID_ENUM */; |
| return 0; |
| } |
| |
| ret = allocate(intArrayFromString(ret), 'i8', ALLOC_NORMAL); |
| AL.alcStringCache[param] = ret; |
| return ret; |
| }, |
| |
| alcGetIntegerv__proxy: 'sync', |
| alcGetIntegerv__sig: 'viiii', |
| alcGetIntegerv: function(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 = 0xA001 /* 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 = 0xA001 /* 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 = 0xA001 /* 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 = 0xA001 /* 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 = 0xA001 /* 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 = 0xA001 /* ALC_INVALID_DEVICE */; |
| return; |
| } |
| {{{ makeSetValue('pValues', '0', '1', 'i32') }}}; |
| break; |
| case 0x20003 /* ALC_MAX_AUXILIARY_SENDS */: |
| if (!(deviceId in AL.deviceRefCounts)) { |
| AL.alcErr = 0xA001 /* 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 |
| console.log('alcGetIntegerv() with param 0x' + param.toString(16) + ' not implemented yet'); |
| #endif |
| AL.alcErr = 0xA003 /* ALC_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| emscripten_alcDevicePauseSOFT__proxy: 'sync', |
| emscripten_alcDevicePauseSOFT__sig: 'vi', |
| emscripten_alcDevicePauseSOFT: function(deviceId) { |
| if (!(deviceId in AL.deviceRefCounts)) { |
| #if OPENAL_DEBUG |
| console.log('alcDevicePauseSOFT() called with an invalid device'); |
| #endif |
| AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */; |
| return; |
| } |
| |
| if (AL.paused) { |
| return; |
| } |
| AL.paused = true; |
| |
| for (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: function(deviceId) { |
| if (!(deviceId in AL.deviceRefCounts)) { |
| #if OPENAL_DEBUG |
| console.log('alcDeviceResumeSOFT() called with an invalid device'); |
| #endif |
| AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */; |
| return; |
| } |
| |
| if (!AL.paused) { |
| return; |
| } |
| AL.paused = false; |
| |
| for (ctxId in AL.contexts) { |
| var ctx = AL.contexts[ctxId]; |
| if (ctx.deviceId !== deviceId) { |
| continue; |
| } |
| |
| ctx.interval = setInterval(function() { AL.scheduleContextAudio(ctx); }, AL.QUEUE_INTERVAL); |
| ctx.audioCtx.resume(); |
| } |
| }, |
| |
| emscripten_alcGetStringiSOFT__proxy: 'sync', |
| emscripten_alcGetStringiSOFT__sig: 'iiii', |
| emscripten_alcGetStringiSOFT__deps: ['alcGetString'], |
| emscripten_alcGetStringiSOFT: function(deviceId, param, index) { |
| if (!(deviceId in AL.deviceRefCounts)) { |
| #if OPENAL_DEBUG |
| console.log('alcGetStringiSOFT() called with an invalid device'); |
| #endif |
| AL.alcErr = 0xA001 /* 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 |
| console.log('alcGetStringiSOFT() with param ALC_HRTF_SPECIFIER_SOFT index ' + index + ' is out of range'); |
| #endif |
| AL.alcErr = 0xA004 /* ALC_INVALID_VALUE */; |
| return 0; |
| } |
| break; |
| default: |
| if (index === 0) { |
| return _alcGetString(deviceId, param); |
| } else { |
| #if OPENAL_DEBUG |
| console.log('alcGetStringiSOFT() with param 0x' + param.toString(16) + ' not implemented yet'); |
| #endif |
| AL.alcErr = 0xA003 /* ALC_INVALID_ENUM */; |
| return 0; |
| } |
| } |
| |
| ret = allocate(intArrayFromString(ret), 'i8', ALLOC_NORMAL); |
| AL.alcStringCache[param] = ret; |
| return ret; |
| }, |
| |
| emscripten_alcResetDeviceSOFT__proxy: 'sync', |
| emscripten_alcResetDeviceSOFT__sig: 'iii', |
| emscripten_alcResetDeviceSOFT: function(deviceId, pAttrList) { |
| if (!(deviceId in AL.deviceRefCounts)) { |
| #if OPENAL_DEBUG |
| console.log('alcResetDeviceSOFT() called with an invalid device'); |
| #endif |
| AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */; |
| return 0 /* 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 === 1 /* ALC_TRUE */) { |
| hrtf = true; |
| } else if (val === 0 /* 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 1 /* ALC_TRUE */; |
| }, |
| |
| // *************************************************************************** |
| // ** AL API |
| // *************************************************************************** |
| |
| // ------------------------------------------------------- |
| // -- AL Resources |
| // ------------------------------------------------------- |
| |
| alGenBuffers__proxy: 'sync', |
| alGenBuffers__sig: 'vii', |
| alGenBuffers: function(count, pBufferIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('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__sig: 'vii', |
| alDeleteBuffers: function(count, pBufferIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('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 |
| console.error('alDeleteBuffers() called with an invalid buffer'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| |
| // Make sure the buffer is no longer in use. |
| if (AL.buffers[bufId].refCount) { |
| #if OPENAL_DEBUG |
| console.error('alDeleteBuffers() called with a used buffer'); |
| #endif |
| AL.currentCtx.err = 0xA004 /* 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__sig: 'vii', |
| alGenSources: function(count, pSourceIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('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: 0x1011 /* AL_INITIAL */, |
| bufQueue: [AL.buffers[0]], |
| audioQueue: [], |
| looping: false, |
| pitch: 1.0, |
| dopplerShift: 1.0, |
| gain: 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__sig: 'vii', |
| alDeleteSources: function(count, pSourceIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('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 |
| console.error('alDeleteSources() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| } |
| |
| for (var i = 0; i < count; ++i) { |
| var srcId = {{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}; |
| AL.setSourceState(AL.currentCtx.sources[srcId], 0x1014 /* AL_STOPPED */); |
| _alSourcei(srcId, 0x1009 /* AL_BUFFER */, 0); |
| delete AL.currentCtx.sources[srcId]; |
| AL.freeIds.push(srcId); |
| } |
| }, |
| |
| // ------------------------------------------------------- |
| // --- AL Context State |
| // ------------------------------------------------------- |
| |
| alGetError__proxy: 'sync', |
| alGetError__sig: 'i', |
| alGetError: function() { |
| if (!AL.currentCtx) { |
| return 0xA004 /* AL_INVALID_OPERATION */; |
| } else { |
| // Reset error on get. |
| var err = AL.currentCtx.err; |
| AL.currentCtx.err = 0 /* AL_NO_ERROR */; |
| return err; |
| } |
| }, |
| |
| alIsExtensionPresent__proxy: 'sync', |
| alIsExtensionPresent__sig: 'ii', |
| alIsExtensionPresent: function(pExtName) { |
| name = UTF8ToString(pExtName); |
| |
| return AL.AL_EXTENSIONS[name] ? 1 : 0; |
| }, |
| |
| alGetProcAddress__deps: ['emscripten_GetAlProcAddress'], |
| alGetProcAddress__proxy: 'sync', |
| alGetProcAddress__sig: 'vi', |
| alGetProcAddress: function(pProcName) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alGetProcAddress() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pProcName) { |
| #if OPENAL_DEBUG |
| console.error('alcGetProcAddress() called with null name pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return 0; /* ALC_NONE */ |
| } |
| return _emscripten_GetAlProcAddress(pProcName); |
| }, |
| |
| alGetEnumValue__proxy: 'sync', |
| alGetEnumValue__sig: 'ii', |
| alGetEnumValue: function(pEnumName) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alGetEnumValue() called without a valid context'); |
| #endif |
| return 0; |
| } |
| |
| if (!pEnumName) { |
| #if OPENAL_DEBUG |
| console.error('alGetEnumValue() called with null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return 0 /* AL_NONE */; |
| } |
| 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 |
| console.error('No value for `' + name + '` is known by alGetEnumValue()'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return 0; |
| } |
| }, |
| |
| alGetString__proxy: 'sync', |
| alGetString__sig: 'ii', |
| alGetString: function(param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alGetString() called without a valid context'); |
| #endif |
| return 0; |
| } |
| |
| if (AL.stringCache[param]) { |
| return AL.stringCache[param]; |
| } |
| |
| var ret; |
| switch (param) { |
| case 0 /* AL_NO_ERROR */: |
| ret = 'No Error'; |
| break; |
| case 0xA001 /* AL_INVALID_NAME */: |
| ret = 'Invalid Name'; |
| break; |
| case 0xA002 /* AL_INVALID_ENUM */: |
| ret = 'Invalid Enum'; |
| break; |
| case 0xA003 /* AL_INVALID_VALUE */: |
| ret = 'Invalid Value'; |
| break; |
| case 0xA004 /* 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 = ''; |
| for (var ext in AL.AL_EXTENSIONS) { |
| ret = ret.concat(ext); |
| ret = ret.concat(' '); |
| } |
| ret = ret.trim(); |
| break; |
| default: |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return 0; |
| } |
| |
| ret = allocate(intArrayFromString(ret), 'i8', ALLOC_NORMAL); |
| AL.stringCache[param] = ret; |
| return ret; |
| }, |
| |
| alEnable__proxy: 'sync', |
| alEnable__sig: 'vi', |
| alEnable: function(param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alEnable() called without a valid context'); |
| #endif |
| return; |
| } |
| switch (param) { |
| case 'AL_SOURCE_DISTANCE_MODEL': |
| AL.currentCtx.sourceDistanceModel = true; |
| AL.updateContextGlobal(AL.currentCtx); |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alEnable() with param 0x' + param.toString(16) + ' not implemented yet'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alDisable__proxy: 'sync', |
| alDisable__sig: 'vi', |
| alDisable: function(param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alDisable() called without a valid context'); |
| #endif |
| return; |
| } |
| switch (pname) { |
| case 'AL_SOURCE_DISTANCE_MODEL': |
| AL.currentCtx.sourceDistanceModel = false; |
| AL.updateContextGlobal(AL.currentCtx); |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alDisable() with param 0x' + param.toString(16) + ' not implemented yet'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alIsEnabled__proxy: 'sync', |
| alIsEnabled__sig: 'ii', |
| alIsEnabled: function(param) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alIsEnabled() called without a valid context'); |
| #endif |
| return 0; |
| } |
| switch (pname) { |
| case 'AL_SOURCE_DISTANCE_MODEL': |
| return AL.currentCtx.sourceDistanceModel ? 0 /* AL_FALSE */ : 1 /* AL_TRUE */; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alIsEnabled() with param 0x' + param.toString(16) + ' not implemented yet'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return 0; |
| } |
| }, |
| |
| alGetDouble__proxy: 'sync', |
| alGetDouble__sig: 'di', |
| alGetDouble: function(param) { |
| var val = AL.getGlobalParam('alGetDouble', param); |
| if (val === null) { |
| return 0.0; |
| } |
| |
| switch (param) { |
| case 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| return val; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetDouble(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return 0.0; |
| } |
| }, |
| |
| alGetDoublev__proxy: 'sync', |
| alGetDoublev__sig: 'vii', |
| alGetDoublev: function(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 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| {{{ makeSetValue('pValues', '0', 'val', 'double') }}}; |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetDoublev(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetFloat__proxy: 'sync', |
| alGetFloat__sig: 'fi', |
| alGetFloat: function(param) { |
| var val = AL.getGlobalParam('alGetFloat', param); |
| if (val === null) { |
| return 0.0; |
| } |
| |
| switch (param) { |
| case 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| return val; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetFloat(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| return 0.0; |
| } |
| }, |
| |
| alGetFloatv__proxy: 'sync', |
| alGetFloatv__sig: 'vii', |
| alGetFloatv: function(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 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| {{{ makeSetValue('pValues', '0', 'val', 'float') }}}; |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetFloatv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetInteger__proxy: 'sync', |
| alGetInteger__sig: 'ii', |
| alGetInteger: function(param) { |
| var val = AL.getGlobalParam('alGetInteger', param); |
| if (val === null) { |
| return 0; |
| } |
| |
| switch (param) { |
| case 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| return val; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetInteger(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return 0; |
| } |
| }, |
| |
| alGetIntegerv__proxy: 'sync', |
| alGetIntegerv__sig: 'vii', |
| alGetIntegerv: function(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 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetIntegerv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetBoolean__proxy: 'sync', |
| alGetBoolean__sig: 'ii', |
| alGetBoolean: function(param) { |
| var val = AL.getGlobalParam('alGetBoolean', param); |
| if (val === null) { |
| return 0 /* AL_FALSE */; |
| } |
| |
| switch (param) { |
| case 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| return val !== 0 ? 1 /* AL_TRUE */ : 0 /* AL_FALSE */; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetBoolean(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return 0 /* AL_FALSE */; |
| } |
| }, |
| |
| alGetBooleanv__proxy: 'sync', |
| alGetBooleanv__sig: 'vii', |
| alGetBooleanv: function(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 0xC000 /* AL_DOPPLER_FACTOR */: |
| case 0xC003 /* AL_SPEED_OF_SOUND */: |
| case 0xD000 /* AL_DISTANCE_MODEL */: |
| {{{ makeSetValue('pValues', '0', 'val', 'i8') }}}; |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetBooleanv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alDistanceModel__proxy: 'sync', |
| alDistanceModel__sig: 'vi', |
| alDistanceModel: function(model) { |
| AL.setGlobalParam('alDistanceModel', 0xD000 /* AL_DISTANCE_MODEL */, model); |
| }, |
| |
| alSpeedOfSound__proxy: 'sync', |
| alSpeedOfSound__sig: 'vi', |
| alSpeedOfSound: function(value) { |
| AL.setGlobalParam('alSpeedOfSound', 0xC003 /* AL_SPEED_OF_SOUND */, value); |
| }, |
| |
| alDopplerFactor__proxy: 'sync', |
| alDopplerFactor__sig: 'vi', |
| alDopplerFactor: function(value) { |
| AL.setGlobalParam('alDopplerFactor', 0xC000 /* 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__sig: 'vi', |
| alDopplerVelocity: function(value) { |
| warnOnce('alDopplerVelocity() is deprecated, and only kept for compatibility with OpenAL 1.0. Use alSpeedOfSound() instead.'); |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alDopplerVelocity() called without a valid context'); |
| #endif |
| return; |
| } |
| if (value <= 0) { // Negative or zero values are disallowed |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| }, |
| |
| // ------------------------------------------------------- |
| // -- AL Listener State |
| // ------------------------------------------------------- |
| |
| alGetListenerf__proxy: 'sync', |
| alGetListenerf__sig: 'vii', |
| alGetListenerf: function(param, pValue) { |
| var val = AL.getListenerParam('alGetListenerf', param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue) { |
| #if OPENAL_DEBUG |
| console.error('alGetListenerf() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x100A /* AL_GAIN */: |
| {{{ makeSetValue('pValue', '0', 'val', 'float') }}}; |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetListenerf(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetListener3f__proxy: 'sync', |
| alGetListener3f__sig: 'viiii', |
| alGetListener3f: function(param, pValue0, pValue1, pValue2) { |
| var val = AL.getListenerParam('alGetListener3f', param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue0 || !pValue1 || !pValue2) { |
| #if OPENAL_DEBUG |
| console.error('alGetListener3f() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* 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 |
| console.error('alGetListener3f(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetListenerfv__proxy: 'sync', |
| alGetListenerfv__sig: 'vii', |
| alGetListenerfv: function(param, pValues) { |
| var val = AL.getListenerParam('alGetListenerfv', param); |
| if (val === null) { |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alGetListenerfv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* AL_VELOCITY */: |
| {{{ makeSetValue('pValues', '0', 'val[0]', 'float') }}}; |
| {{{ makeSetValue('pValues', '4', 'val[1]', 'float') }}}; |
| {{{ makeSetValue('pValues', '8', 'val[2]', 'float') }}}; |
| break; |
| case 0x100F /* 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 |
| console.error('alGetListenerfv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetListeneri__proxy: 'sync', |
| alGetListeneri__sig: 'vii', |
| alGetListeneri: function(param, pValue) { |
| var val = AL.getListenerParam('alGetListeneri', param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue) { |
| #if OPENAL_DEBUG |
| console.error('alGetListeneri() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| #if OPENAL_DEBUG |
| console.error('alGetListeneri(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| }, |
| |
| alGetListener3i__proxy: 'sync', |
| alGetListener3i__sig: 'viiii', |
| alGetListener3i: function(param, pValue0, pValue1, pValue2) { |
| var val = AL.getListenerParam('alGetListener3i', param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue0 || !pValue1 || !pValue2) { |
| #if OPENAL_DEBUG |
| console.error('alGetListener3i() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* 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 |
| console.error('alGetListener3i(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetListeneriv__proxy: 'sync', |
| alGetListeneriv__sig: 'vii', |
| alGetListeneriv: function(param, pValues) { |
| var val = AL.getListenerParam('alGetListeneriv', param); |
| if (val === null) { |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alGetListeneriv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* AL_VELOCITY */: |
| {{{ makeSetValue('pValues', '0', 'val[0]', 'i32') }}}; |
| {{{ makeSetValue('pValues', '4', 'val[1]', 'i32') }}}; |
| {{{ makeSetValue('pValues', '8', 'val[2]', 'i32') }}}; |
| break; |
| case 0x100F /* 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 |
| console.error('alGetListeneriv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alListenerf__proxy: 'sync', |
| alListenerf__sig: 'vif', |
| alListenerf: function(param, value) { |
| switch (param) { |
| case 0x100A /* AL_GAIN */: |
| AL.setListenerParam('alListenerf', param, value); |
| break; |
| default: |
| AL.setListenerParam('alListenerf', param, null); |
| break; |
| } |
| }, |
| |
| alListener3f__proxy: 'sync', |
| alListener3f__sig: 'vifff', |
| alListener3f: function(param, value0, value1, value2) { |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* 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__sig: 'vii', |
| alListenerfv: function(param, pValues) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alListenerfv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alListenerfv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* 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 0x100F /* 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__sig: 'vii', |
| alListeneri: function(param, value) { |
| AL.setListenerParam('alListeneri', param, null); |
| }, |
| |
| alListener3i__proxy: 'sync', |
| alListener3i__sig: 'viiii', |
| alListener3i: function(param, value0, value1, value2) { |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* 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__sig: 'vii', |
| alListeneriv: function(param, pValues) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alListeneriv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alListeneriv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1006 /* 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 0x100F /* 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__sig: 'ii', |
| alIsBuffer: function(bufferId) { |
| if (!AL.currentCtx) { |
| return false; |
| } |
| if (bufferId > AL.buffers.length) { |
| return false; |
| } |
| |
| if (!AL.buffers[bufferId]) { |
| return false; |
| } else { |
| return true; |
| } |
| }, |
| |
| alBufferData__proxy: 'sync', |
| alBufferData__sig: 'viiiii', |
| alBufferData: function(bufferId, format, pData, size, freq) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alBufferData() called without a valid context'); |
| #endif |
| return; |
| } |
| var buf = AL.buffers[bufferId]; |
| if (!buf) { |
| #if OPENAL_DEBUG |
| console.error('alBufferData() called with an invalid buffer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| if (freq <= 0) { |
| #if OPENAL_DEBUG |
| console.error('alBufferData() called with an invalid frequency'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error('alBufferData() called with invalid format ' + format); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| buf.frequency = freq; |
| buf.audioBuf = audioBuf; |
| } catch (e) { |
| #if OPENAL_DEBUG |
| console.error('alBufferData() upload failed with an exception ' + e); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| }, |
| |
| alGetBufferf__proxy: 'sync', |
| alGetBufferf__sig: 'viii', |
| alGetBufferf: function(bufferId, param, pValue) { |
| var val = AL.getBufferParam('alGetBufferf', bufferId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue) { |
| #if OPENAL_DEBUG |
| console.error('alGetBufferf() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| #if OPENAL_DEBUG |
| console.error('alGetBufferf(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| }, |
| |
| alGetBuffer3f__proxy: 'sync', |
| alGetBuffer3f__sig: 'viiiii', |
| alGetBuffer3f: function(bufferId, param, pValue0, pValue1, pValue2) { |
| var val = AL.getBufferParam('alGetBuffer3f', bufferId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue0 || !pValue1 || !pValue2) { |
| #if OPENAL_DEBUG |
| console.error('alGetBuffer3f() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| #if OPENAL_DEBUG |
| console.error('alGetBuffer3f(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| }, |
| |
| alGetBufferfv__proxy: 'sync', |
| alGetBufferfv__sig: 'viii', |
| alGetBufferfv: function(bufferId, param, pValues) { |
| var val = AL.getBufferParam('alGetBufferfv', bufferId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alGetBufferfv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| #if OPENAL_DEBUG |
| console.error('alGetBufferfv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| }, |
| |
| alGetBufferi__proxy: 'sync', |
| alGetBufferi__sig: 'viii', |
| alGetBufferi: function(bufferId, param, pValue) { |
| var val = AL.getBufferParam('alGetBufferi', bufferId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue) { |
| #if OPENAL_DEBUG |
| console.error('alGetBufferi() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error('alGetBufferi(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetBuffer3i__proxy: 'sync', |
| alGetBuffer3i__sig: 'viiiii', |
| alGetBuffer3i: function(bufferId, param, pValue0, pValue1, pValue2) { |
| var val = AL.getBufferParam('alGetBuffer3i', bufferId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue0 || !pValue1 || !pValue2) { |
| #if OPENAL_DEBUG |
| console.error('alGetBuffer3i() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| #if OPENAL_DEBUG |
| console.error('alGetBuffer3i(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| }, |
| |
| alGetBufferiv__proxy: 'sync', |
| alGetBufferiv__sig: 'viii', |
| alGetBufferiv: function(bufferId, param, pValues) { |
| var val = AL.getBufferParam('alGetBufferiv', bufferId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alGetBufferiv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 |
| console.error('alGetBufferiv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* 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__sig: 'viif', |
| alBufferf: function(bufferId, param, value) { |
| AL.setBufferParam('alBufferf', bufferId, param, null); |
| }, |
| |
| alBuffer3f__proxy: 'sync', |
| alBuffer3f__sig: 'viifff', |
| alBuffer3f: function(bufferId, param, value0, value1, value2) { |
| AL.setBufferParam('alBuffer3f', bufferId, param, null); |
| }, |
| |
| alBufferfv__proxy: 'sync', |
| alBufferfv__sig: 'viii', |
| alBufferfv: function(bufferId, param, pValues) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alBufferfv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alBufferfv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| AL.setBufferParam('alBufferfv', bufferId, param, null); |
| }, |
| |
| alBufferi__proxy: 'sync', |
| alBufferi__sig: 'viii', |
| alBufferi: function(bufferId, param, value) { |
| AL.setBufferParam('alBufferi', bufferId, param, null); |
| }, |
| |
| alBuffer3i__proxy: 'sync', |
| alBuffer3i__sig: 'viiiii', |
| alBuffer3i: function(bufferId, param, value0, value1, value2) { |
| AL.setBufferParam('alBuffer3i', bufferId, param, null); |
| }, |
| |
| alBufferiv__proxy: 'sync', |
| alBufferiv__sig: 'viii', |
| alBufferiv: function(bufferId, param, pValues) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alBufferiv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alBufferiv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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__sig: 'ii', |
| alIsSource: function(sourceId) { |
| if (!AL.currentCtx) { |
| return false; |
| } |
| |
| if (!AL.currentCtx.sources[sourceId]) { |
| return false; |
| } else { |
| return true; |
| } |
| }, |
| |
| alSourceQueueBuffers__proxy: 'sync', |
| alSourceQueueBuffers__sig: 'viii', |
| alSourceQueueBuffers: function(sourceId, count, pBufferIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceQueueBuffers() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourceQueueBuffers() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| if (src.type === 0x1028 /* AL_STATIC */) { |
| #if OPENAL_DEBUG |
| console.error('alSourceQueueBuffers() called while a static buffer is bound'); |
| #endif |
| AL.currentCtx.err = 0xA004 /* 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 i = 0; i < src.bufQueue.length; i++) { |
| if (src.bufQueue[i].id !== 0) { |
| templateBuf = src.bufQueue[i]; |
| 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 |
| console.error('alSourceQueueBuffers() called with an invalid buffer'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* 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 |
| console.error('alSourceQueueBuffers() called with a buffer of different format'); |
| #endif |
| AL.currentCtx.err = 0xA004 /* 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__sig: 'viii', |
| alSourceUnqueueBuffers: function(sourceId, count, pBufferIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceUnqueueBuffers() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourceUnqueueBuffers() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| if (count > (src.bufQueue.length === 1 && src.bufQueue[0].id === 0 ? 0 : src.bufsProcessed)) { |
| AL.currentCtx.err = 0xA003 /* 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__sig: 'vi', |
| alSourcePlay: function(sourceId) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePlay() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePlay() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| AL.setSourceState(src, 0x1012 /* AL_PLAYING */); |
| }, |
| |
| alSourcePlayv__proxy: 'sync', |
| alSourcePlayv__sig: 'vii', |
| alSourcePlayv: function(count, pSourceIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePlayv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pSourceIds) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePlayv() called with null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| } |
| for (var i = 0; i < count; ++i) { |
| if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePlayv() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| } |
| |
| for (var i = 0; i < count; ++i) { |
| AL.setSourceState({{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}, 0x1012 /* AL_PLAYING */); |
| } |
| }, |
| |
| alSourceStop__proxy: 'sync', |
| alSourceStop__sig: 'vi', |
| alSourceStop: function(sourceId) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceStop() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourceStop() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| AL.setSourceState(src, 0x1014 /* AL_STOPPED */); |
| }, |
| |
| alSourceStopv__proxy: 'sync', |
| alSourceStopv__sig: 'vii', |
| alSourceStopv: function(count, pSourceIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceStopv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pSourceIds) { |
| #if OPENAL_DEBUG |
| console.error('alSourceStopv() called with null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| } |
| for (var i = 0; i < count; ++i) { |
| if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { |
| #if OPENAL_DEBUG |
| console.error('alSourceStopv() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| } |
| |
| for (var i = 0; i < count; ++i) { |
| AL.setSourceState({{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}, 0x1014 /* AL_STOPPED */); |
| } |
| }, |
| |
| alSourceRewind__proxy: 'sync', |
| alSourceRewind__sig: 'vi', |
| alSourceRewind: function(sourceId) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceRewind() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourceRewind() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| // Stop the source first to clear the source queue |
| AL.setSourceState(src, 0x1014 /* AL_STOPPED */); |
| // Now set the state of AL_INITIAL according to the specification |
| AL.setSourceState(src, 0x1011 /* AL_INITIAL */); |
| }, |
| |
| alSourceRewindv__proxy: 'sync', |
| alSourceRewindv__sig: 'vii', |
| alSourceRewindv: function(count, pSourceIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceRewindv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pSourceIds) { |
| #if OPENAL_DEBUG |
| console.error('alSourceRewindv() called with null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| } |
| for (var i = 0; i < count; ++i) { |
| if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { |
| #if OPENAL_DEBUG |
| console.error('alSourceRewindv() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| } |
| |
| for (var i = 0; i < count; ++i) { |
| AL.setSourceState({{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}, 0x1011 /* AL_INITIAL */); |
| } |
| }, |
| |
| alSourcePause__proxy: 'sync', |
| alSourcePause__sig: 'vi', |
| alSourcePause: function(sourceId) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePause() called without a valid context'); |
| #endif |
| return; |
| } |
| var src = AL.currentCtx.sources[sourceId]; |
| if (!src) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePause() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| AL.setSourceState(src, 0x1013 /* AL_PAUSED */); |
| }, |
| |
| alSourcePausev__proxy: 'sync', |
| alSourcePausev__sig: 'vii', |
| alSourcePausev: function(count, pSourceIds) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePausev() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pSourceIds) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePausev() called with null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| } |
| for (var i = 0; i < count; ++i) { |
| if (!AL.currentCtx.sources[{{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}]) { |
| #if OPENAL_DEBUG |
| console.error('alSourcePausev() called with an invalid source'); |
| #endif |
| AL.currentCtx.err = 0xA001 /* AL_INVALID_NAME */; |
| return; |
| } |
| } |
| |
| for (var i = 0; i < count; ++i) { |
| AL.setSourceState({{{ makeGetValue('pSourceIds', 'i*4', 'i32') }}}, 0x1013 /* AL_PAUSED */); |
| } |
| }, |
| |
| alGetSourcef__proxy: 'sync', |
| alGetSourcef__sig: 'viii', |
| alGetSourcef: function(sourceId, param, pValue) { |
| var val = AL.getSourceParam('alGetSourcef', sourceId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue) { |
| #if OPENAL_DEBUG |
| console.error('alGetSourcef() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1001 /* AL_CONE_INNER_ANGLE */: |
| case 0x1002 /* AL_CONE_OUTER_ANGLE */: |
| case 0x1003 /* AL_PITCH */: |
| case 0x100A /* 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 |
| console.error('alGetSourcef(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetSource3f__proxy: 'sync', |
| alGetSource3f__sig: 'viiiii', |
| alGetSource3f: function(sourceId, param, pValue0, pValue1, pValue2) { |
| var val = AL.getSourceParam('alGetSource3f', sourceId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue0 || !pValue1 || !pValue2) { |
| #if OPENAL_DEBUG |
| console.error('alGetSource3f() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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 |
| console.error('alGetSource3f(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetSourcefv__proxy: 'sync', |
| alGetSourcefv__sig: 'viii', |
| alGetSourcefv: function(sourceId, param, pValues) { |
| var val = AL.getSourceParam('alGetSourcefv', sourceId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alGetSourcefv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1001 /* AL_CONE_INNER_ANGLE */: |
| case 0x1002 /* AL_CONE_OUTER_ANGLE */: |
| case 0x1003 /* AL_PITCH */: |
| case 0x100A /* 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 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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 |
| console.error('alGetSourcefv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetSourcei__proxy: 'sync', |
| alGetSourcei__sig: 'viii', |
| alGetSourcei: function(sourceId, param, pValue) { |
| var val = AL.getSourceParam('alGetSourcei', sourceId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue) { |
| #if OPENAL_DEBUG |
| console.error('alGetSourcei() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 0xD000 /* AL_DISTANCE_MODEL */: |
| {{{ makeSetValue('pValue', '0', 'val', 'i32') }}}; |
| break; |
| default: |
| #if OPENAL_DEBUG |
| console.error('alGetSourcei(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetSource3i__proxy: 'sync', |
| alGetSource3i__sig: 'viiiii', |
| alGetSource3i: function(source, param, pValue0, pValue1, pValue2) { |
| var val = AL.getSourceParam('alGetSource3i', sourceId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValue0 || !pValue1 || !pValue2) { |
| #if OPENAL_DEBUG |
| console.error('alGetSource3i() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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 |
| console.error('alGetSource3i(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alGetSourceiv__proxy: 'sync', |
| alGetSourceiv__sig: 'viii', |
| alGetSourceiv: function(sourceId, param, pValues) { |
| var val = AL.getSourceParam('alGetSourceiv', sourceId, param); |
| if (val === null) { |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alGetSourceiv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 0xD000 /* AL_DISTANCE_MODEL */: |
| {{{ makeSetValue('pValues', '0', 'val', 'i32') }}}; |
| break; |
| case 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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 |
| console.error('alGetSourceiv(): param 0x' + param.toString(16) + ' has wrong signature'); |
| #endif |
| AL.currentCtx.err = 0xA002 /* AL_INVALID_ENUM */; |
| return; |
| } |
| }, |
| |
| alSourcef__proxy: 'sync', |
| alSourcef__sig: 'viif', |
| alSourcef: function(sourceId, param, value) { |
| switch (param) { |
| case 0x1001 /* AL_CONE_INNER_ANGLE */: |
| case 0x1002 /* AL_CONE_OUTER_ANGLE */: |
| case 0x1003 /* AL_PITCH */: |
| case 0x100A /* 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__sig: 'viifff', |
| alSource3f: function(sourceId, param, value0, value1, value2) { |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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__sig: 'viii', |
| alSourcefv: function(sourceId, param, pValues) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourcefv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alSourcefv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* AL_INVALID_VALUE */; |
| return; |
| } |
| |
| switch (param) { |
| case 0x1001 /* AL_CONE_INNER_ANGLE */: |
| case 0x1002 /* AL_CONE_OUTER_ANGLE */: |
| case 0x1003 /* AL_PITCH */: |
| case 0x100A /* 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 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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__sig: 'viii', |
| alSourcei: function(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 0xD000 /* AL_DISTANCE_MODEL */: |
| AL.setSourceParam('alSourcei', sourceId, param, value); |
| break; |
| default: |
| AL.setSourceParam('alSourcei', sourceId, param, null); |
| break; |
| } |
| }, |
| |
| alSource3i__proxy: 'sync', |
| alSource3i__sig: 'viiiii', |
| alSource3i: function(sourceId, param, value0, value1, value2) { |
| switch (param) { |
| case 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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__sig: 'viii', |
| alSourceiv: function(source, param, pValues) { |
| if (!AL.currentCtx) { |
| #if OPENAL_DEBUG |
| console.error('alSourceiv() called without a valid context'); |
| #endif |
| return; |
| } |
| if (!pValues) { |
| #if OPENAL_DEBUG |
| console.error('alSourceiv() called with a null pointer'); |
| #endif |
| AL.currentCtx.err = 0xA003 /* 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 0xD000 /* AL_DISTANCE_MODEL */: |
| var val = {{{ makeGetValue('pValues', '0', 'i32') }}}; |
| AL.setSourceParam('alSourceiv', sourceId, param, val); |
| break; |
| case 0x1004 /* AL_POSITION */: |
| case 0x1005 /* AL_DIRECTION */: |
| case 0x1006 /* 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'); |
| mergeInto(LibraryManager.library, LibraryOpenAL); |
| |