blob: 5edd31810f2a4905ac0f4514b372685cd14bc688 [file] [log] [blame] [edit]
//"use strict";
var LibraryOpenAL = {
$AL__deps: ['$Browser'],
$AL: {
contexts: [],
currentContext: null,
alcErr: 0,
stringCache: {},
alcStringCache: {},
QUEUE_INTERVAL: 25,
QUEUE_LOOKAHEAD: 100,
newSrcId: 1,
#if OPENAL_DEBUG
//This function is slow and used only for debugging purposes
srcIdBySrc: function srcIdBySrc(src) {
var idx = 0;
for (var srcId in AL.currentContext.src) {
if (AL.currentContext.src[srcId] == src) {
idx = srcId;
break;
}
}
return idx;
},
#endif
updateSources: function updateSources(context) {
// 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 srcId in context.src) {
AL.updateSource(context.src[srcId]);
}
},
updateSource: function updateSource(src) {
#if OPENAL_DEBUG
var idx = AL.srcIdBySrc(src);
#endif
if (src.state !== 0x1012 /* AL_PLAYING */) {
return;
}
var currentTime = AL.currentContext.ctx.currentTime;
var startTime = src.bufferPosition;
for (var i = src.buffersPlayed; i < src.queue.length; i++) {
var entry = src.queue[i];
var startOffset = startTime - currentTime;
var endTime = startTime + entry.buffer.duration;
// Clean up old buffers.
if (currentTime >= endTime) {
// Update our location in the queue.
src.bufferPosition = endTime;
src.buffersPlayed = i + 1;
// Stop / restart the source when we hit the end.
if (src.buffersPlayed >= src.queue.length) {
if (src.loop) {
AL.setSourceState(src, 0x1012 /* AL_PLAYING */);
} else {
AL.setSourceState(src, 0x1014 /* AL_STOPPED */);
}
}
}
// Process all buffers that'll be played before the next tick.
else if (startOffset < (AL.QUEUE_LOOKAHEAD / 1000) && !entry.src) {
// If the start offset is negative, we need to offset the actual buffer.
var offset = Math.abs(Math.min(startOffset, 0));
entry.src = AL.currentContext.ctx.createBufferSource();
entry.src.buffer = entry.buffer;
entry.src.connect(src.gain);
if (typeof(entry.src.start) !== 'undefined') {
entry.src.start(startTime, offset);
} else if (typeof(entry.src.noteOn) !== 'undefined') {
entry.src.noteOn(startTime);
#if OPENAL_DEBUG
if (offset > 0) {
Runtime.warnOnce('The current browser does not support AudioBufferSourceNode.start(when, offset); method, so cannot play back audio with an offset '+offset+' secs! Audio glitches will occur!');
}
#endif
}
#if OPENAL_DEBUG
else {
Runtime.warnOnce('Unable to start AudioBufferSourceNode playback! Not supported by the browser?');
}
console.log('updateSource queuing buffer ' + i + ' for source ' + idx + ' at ' + startTime + ' (offset by ' + offset + ')');
#endif
}
startTime = endTime;
}
},
setSourceState: function setSourceState(src, state) {
#if OPENAL_DEBUG
var idx = AL.srcIdBySrc(src);
#endif
if (state === 0x1012 /* AL_PLAYING */) {
if (src.state !== 0x1013 /* AL_PAUSED */) {
src.state = 0x1012 /* AL_PLAYING */;
// Reset our position.
src.bufferPosition = AL.currentContext.ctx.currentTime;
src.buffersPlayed = 0;
#if OPENAL_DEBUG
console.log('setSourceState resetting and playing source ' + idx);
#endif
} else {
src.state = 0x1012 /* AL_PLAYING */;
// Use the current offset from src.bufferPosition to resume at the correct point.
src.bufferPosition = AL.currentContext.ctx.currentTime - src.bufferPosition;
#if OPENAL_DEBUG
console.log('setSourceState resuming source ' + idx + ' at ' + src.bufferPosition.toFixed(4));
#endif
}
AL.stopSourceQueue(src);
AL.updateSource(src);
} else if (state === 0x1013 /* AL_PAUSED */) {
if (src.state === 0x1012 /* AL_PLAYING */) {
src.state = 0x1013 /* AL_PAUSED */;
// Store off the current offset to restore with on resume.
src.bufferPosition = AL.currentContext.ctx.currentTime - src.bufferPosition;
AL.stopSourceQueue(src);
#if OPENAL_DEBUG
console.log('setSourceState pausing source ' + idx + ' at ' + src.bufferPosition.toFixed(4));
#endif
}
} else if (state === 0x1014 /* AL_STOPPED */) {
if (src.state !== 0x1011 /* AL_INITIAL */) {
src.state = 0x1014 /* AL_STOPPED */;
src.buffersPlayed = src.queue.length;
AL.stopSourceQueue(src);
#if OPENAL_DEBUG
console.log('setSourceState stopping source ' + idx);
#endif
}
} else if (state == 0x1011 /* AL_INITIAL */) {
if (src.state !== 0x1011 /* AL_INITIAL */) {
src.state = 0x1011 /* AL_INITIAL */;
src.bufferPosition = 0;
src.buffersPlayed = 0;
#if OPENAL_DEBUG
console.log('setSourceState initializing source ' + idx);
#endif
}
}
},
stopSourceQueue: function stopSourceQueue(src) {
for (var i = 0; i < src.queue.length; i++) {
var entry = src.queue[i];
if (entry.src) {
entry.src.stop(0);
entry.src = null;
}
}
}
},
alcProcessContext: function(context) {},
alcSuspendContext: function(context) {},
alcMakeContextCurrent: function(context) {
if (context == 0) {
AL.currentContext = null;
return 0;
} else {
AL.currentContext = AL.contexts[context - 1];
return 1;
}
},
alcGetContextsDevice: function(context) {
if (context <= AL.contexts.length && context > 0) {
// Returns the only one audio device
return 1;
}
return 0;
},
alcGetCurrentContext: function() {
for (var i = 0; i < AL.contexts.length; ++i) {
if (AL.contexts[i] == AL.currentContext) {
return i + 1;
}
}
return 0;
},
alcDestroyContext: function(context) {
// Stop playback, etc
clearInterval(AL.contexts[context - 1].interval);
},
alcCloseDevice: function(device) {
// Stop playback, etc
},
alcOpenDevice: function(deviceName) {
if (typeof(AudioContext) !== "undefined" ||
typeof(webkitAudioContext) !== "undefined") {
return 1; // non-null pointer -- we just simulate one device
} else {
return 0;
}
},
alcCreateContext: function(device, attrList) {
if (device != 1) {
return 0;
}
if (attrList) {
#if OPENAL_DEBUG
console.log("The attrList argument of alcCreateContext is not supported yet");
#endif
return 0;
}
var ctx;
try {
ctx = new AudioContext();
} catch (e) {
try {
ctx = new webkitAudioContext();
} catch (e) {}
}
if (ctx) {
// Old Web Audio API (e.g. Safari 6.0.5) had an inconsistently named createGainNode function.
if (typeof(ctx.createGain) === 'undefined') ctx.createGain = ctx.createGainNode;
var gain = ctx.createGain();
gain.connect(ctx.destination);
var context = {
ctx: ctx,
err: 0,
src: {},
buf: [],
interval: setInterval(function() { AL.updateSources(context); }, AL.QUEUE_INTERVAL),
gain: gain
};
AL.contexts.push(context);
return AL.contexts.length;
} else {
return 0;
}
},
alGetError: function() {
if (!AL.currentContext) {
return 0xA004 /* AL_INVALID_OPERATION */;
} else {
// Reset error on get.
var err = AL.currentContext.err;
AL.currentContext.err = 0 /* AL_NO_ERROR */;
return err;
}
},
alcGetError: function(device) {
var err = AL.alcErr;
AL.alcErr = 0;
return err;
},
alcGetIntegerv: function(device, param, size, data) {
if (size == 0 || !data) {
AL.currentContext.err = 0xA003 /* AL_INVALID_VALUE */;
return;
}
switch(param) {
case 0x1000 /* ALC_MAJOR_VERSION */:
{{{ makeSetValue('data', '0', '1', 'i32') }}};
break;
case 0x1001 /* ALC_MINOR_VERSION */:
{{{ makeSetValue('data', '0', '1', 'i32') }}};
break;
case 0x1002 /* ALC_ATTRIBUTES_SIZE */:
if (!device) {
AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */;
return 0;
}
{{{ makeSetValue('data', '0', '1', 'i32') }}};
break;
case 0x1003 /* ALC_ALL_ATTRIBUTES */:
if (!device) {
AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */;
return 0;
}
{{{ makeSetValue('data', '0', '0', 'i32') }}};
break;
case 0x20003 /* ALC_MAX_AUXILIARY_SENDS */:
if (!device) {
AL.currentContext.err = 0xA001 /* ALC_INVALID_DEVICE */;
return 0;
}
{{{ makeSetValue('data', '0', '1', 'i32') }}};
default:
#if OPENAL_DEBUG
console.log("alcGetIntegerv with param " + param + " not implemented yet");
#endif
AL.alcErr = 0xA003 /* ALC_INVALID_ENUM */;
break;
}
},
alDeleteSources: function(count, sources) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alDeleteSources called without a valid context");
#endif
return;
}
for (var i = 0; i < count; ++i) {
var sourceIdx = {{{ makeGetValue('sources', 'i*4', 'i32') }}};
delete AL.currentContext.src[sourceIdx];
}
},
alGenSources: function(count, sources) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGenSources called without a valid context");
#endif
return;
}
for (var i = 0; i < count; ++i) {
var gain = AL.currentContext.ctx.createGain();
gain.connect(AL.currentContext.gain);
AL.currentContext.src[AL.newSrcId] = {
state: 0x1011 /* AL_INITIAL */,
queue: [],
loop: false,
get refDistance() {
return this._refDistance || 1;
},
set refDistance(val) {
this._refDistance = val;
if (this.panner) this.panner.refDistance = val;
},
get maxDistance() {
return this._maxDistance || 10000;
},
set maxDistance(val) {
this._maxDistance = val;
if (this.panner) this.panner.maxDistance = val;
},
get rolloffFactor() {
return this._rolloffFactor || 1;
},
set rolloffFactor(val) {
this._rolloffFactor = val;
if (this.panner) this.panner.rolloffFactor = val;
},
get position() {
return this._position || [0, 0, 0];
},
set position(val) {
this._position = val;
if (this.panner) this.panner.setPosition(val[0], val[1], val[2]);
},
get velocity() {
return this._velocity || [0, 0, 0];
},
set velocity(val) {
this._velocity = val;
if (this.panner) this.panner.setVelocity(val[0], val[1], val[2]);
},
get direction() {
return this._direction || [0, 0, 0];
},
set direction(val) {
this._direction = val;
if (this.panner) this.panner.setOrientation(val[0], val[1], val[2]);
},
get coneOuterGain() {
return this._coneOuterGain || 0.0;
},
set coneOuterGain(val) {
this._coneOuterGain = val;
if (this.panner) this.panner.coneOuterGain = val;
},
get coneInnerAngle() {
return this._coneInnerAngle || 360.0;
},
set coneInnerAngle(val) {
this._coneInnerAngle = val;
if (this.panner) this.panner.coneInnerAngle = val;
},
get coneOuterAngle() {
return this._coneOuterAngle || 360.0;
},
set coneOuterAngle(val) {
this._coneOuterAngle = val;
if (this.panner) this.panner.coneOuterAngle = val;
},
gain: gain,
panner: null,
buffersPlayed: 0,
bufferPosition: 0
};
{{{ makeSetValue('sources', 'i*4', 'AL.newSrcId', 'i32') }}};
AL.newSrcId++;
}
},
alIsSource: function(sourceId) {
if (!AL.currentContext) {
return false;
}
if (!AL.currentContext.src[sourceId]) {
return false;
} else {
return true;
}
},
alSourcei: function(source, param, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourcei called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourcei called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
switch (param) {
case 0x1001 /* AL_CONE_INNER_ANGLE */:
src.coneInnerAngle = value;
break;
case 0x1002 /* AL_CONE_OUTER_ANGLE */:
src.coneOuterAngle = value;
break;
case 0x1007 /* AL_LOOPING */:
src.loop = (value === 1 /* AL_TRUE */);
break;
case 0x1009 /* AL_BUFFER */:
var buffer = AL.currentContext.buf[value - 1];
if (value == 0) {
src.queue = [];
} else {
src.queue = [{ buffer: buffer }];
}
AL.updateSource(src);
break;
case 0x202 /* AL_SOURCE_RELATIVE */:
if (value === 1 /* AL_TRUE */) {
if (src.panner) {
src.panner = null;
// Disconnect from the panner.
src.gain.disconnect();
src.gain.connect(AL.currentContext.gain);
}
} else if (value === 0 /* AL_FALSE */) {
if (!src.panner) {
var panner = src.panner = AL.currentContext.ctx.createPanner();
panner.panningModel = "equalpower";
panner.distanceModel = "linear";
panner.refDistance = src.refDistance;
panner.maxDistance = src.maxDistance;
panner.rolloffFactor = src.rolloffFactor;
panner.setPosition(src.position[0], src.position[1], src.position[2]);
panner.setVelocity(src.velocity[0], src.velocity[1], src.velocity[2]);
panner.connect(AL.currentContext.gain);
// Disconnect from the default source.
src.gain.disconnect();
src.gain.connect(panner);
}
} else {
AL.currentContext.err = 0xA003 /* AL_INVALID_VALUE */;
}
break;
default:
#if OPENAL_DEBUG
console.log("alSourcei with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alSourcef: function(source, param, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourcef called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourcef called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
switch (param) {
case 0x1003 /* AL_PITCH */:
#if OPENAL_DEBUG
console.log("alSourcef was called with 0x1003 /* AL_PITCH */, but Web Audio does not support static pitch changes");
#endif
break;
case 0x100A /* AL_GAIN */:
src.gain.gain.value = value;
break;
// case 0x100D /* AL_MIN_GAIN */:
// break;
// case 0x100E /* AL_MAX_GAIN */:
// break;
case 0x1023 /* AL_MAX_DISTANCE */:
src.maxDistance = value;
break;
case 0x1021 /* AL_ROLLOFF_FACTOR */:
src.rolloffFactor = value;
break;
case 0x1022 /* AL_CONE_OUTER_GAIN */:
src.coneOuterGain = value;
break;
case 0x1001 /* AL_CONE_INNER_ANGLE */:
src.coneInnerAngle = value;
break;
case 0x1002 /* AL_CONE_OUTER_ANGLE */:
src.coneOuterAngle = value;
break;
case 0x1020 /* AL_REFERENCE_DISTANCE */:
src.refDistance = value;
break;
default:
#if OPENAL_DEBUG
console.log("alSourcef with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alSource3i: ['alSource3f'],
alSource3i: function(source, param, v1, v2, v3) {
_alSource3f(source, param, v1, v2, v3);
},
alSource3f: function(source, param, v1, v2, v3) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSource3f called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSource3f called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
switch (param) {
case 0x1004 /* AL_POSITION */:
src.position = [v1, v2, v3];
break;
case 0x1005 /* AL_DIRECTION */:
src.direction = [v1, v2, v3];
break;
case 0x1006 /* AL_VELOCITY */:
src.velocity = [v1, v2, v3];
break;
default:
#if OPENAL_DEBUG
console.log("alSource3f with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alSourcefv__deps: ['alSource3f'],
alSourcefv: function(source, param, value) {
_alSource3f(source, param,
{{{ makeGetValue('value', '0', 'float') }}},
{{{ makeGetValue('value', '4', 'float') }}},
{{{ makeGetValue('value', '8', 'float') }}});
},
alSourceQueueBuffers: function(source, count, buffers) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourceQueueBuffers called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourceQueueBuffers called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
for (var i = 0; i < count; ++i) {
var bufferIdx = {{{ makeGetValue('buffers', 'i*4', 'i32') }}};
if (bufferIdx > AL.currentContext.buf.length) {
#if OPENAL_DEBUG
console.error("alSourceQueueBuffers called with an invalid buffer");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
}
for (var i = 0; i < count; ++i) {
var bufferIdx = {{{ makeGetValue('buffers', 'i*4', 'i32') }}};
var buffer = AL.currentContext.buf[bufferIdx - 1];
src.queue.push({ buffer: buffer, src: null });
}
AL.updateSource(src);
},
alSourceUnqueueBuffers: function(source, count, buffers) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourceUnqueueBuffers called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourceUnqueueBuffers called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
if (count > src.buffersPlayed) {
AL.currentContext.err = 0xA003 /* AL_INVALID_VALUE */;
return;
}
for (var i = 0; i < count; i++) {
var entry = src.queue.shift();
// Write the buffers index out to the return list.
for (var j = 0; j < AL.currentContext.buf.length; j++) {
var b = AL.currentContext.buf[j];
if (b && b == entry.buffer) {
{{{ makeSetValue('buffers', 'i*4', 'j+1', 'i32') }}};
break;
}
}
src.buffersPlayed--;
}
AL.updateSource(src);
},
alDeleteBuffers: function(count, buffers)
{
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alDeleteBuffers called without a valid context");
#endif
return;
}
if (count > AL.currentContext.buf.length) {
AL.currentContext.err = 0xA003 /* AL_INVALID_VALUE */;
return;
}
for (var i = 0; i < count; ++i) {
var bufferIdx = {{{ makeGetValue('buffers', 'i*4', 'i32') }}} - 1;
// Make sure the buffer index is valid.
if (bufferIdx >= AL.currentContext.buf.length || !AL.currentContext.buf[bufferIdx]) {
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
// Make sure the buffer is no longer in use.
var buffer = AL.currentContext.buf[bufferIdx];
for (var srcId in AL.currentContext.src) {
var src = AL.currentContext.src[srcId];
if (!src) {
continue;
}
for (var k = 0; k < src.queue.length; k++) {
if (buffer === src.queue[k].buffer) {
AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */;
return;
}
}
}
}
for (var i = 0; i < count; ++i) {
var bufferIdx = {{{ makeGetValue('buffers', 'i*4', 'i32') }}} - 1;
delete AL.currentContext.buf[bufferIdx];
}
},
alGenBuffers: function(count, buffers) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGenBuffers called without a valid context");
#endif
return;
}
for (var i = 0; i < count; ++i) {
AL.currentContext.buf.push(null);
{{{ makeSetValue('buffers', 'i*4', 'AL.currentContext.buf.length', 'i32') }}};
}
},
alIsBuffer: function(bufferId) {
if (!AL.currentContext) {
return false;
}
if (bufferId > AL.currentContext.buf.length) {
return false;
}
if (!AL.currentContext.buf[bufferId - 1]) {
return false;
} else {
return true;
}
},
alBufferData: function(buffer, format, data, size, freq) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alBufferData called without a valid context");
#endif
return;
}
if (buffer > AL.currentContext.buf.length) {
#if OPENAL_DEBUG
console.error("alBufferData called with an invalid buffer");
#endif
return;
}
var channels, bytes;
switch (format) {
case 0x1100 /* AL_FORMAT_MONO8 */:
bytes = 1;
channels = 1;
break;
case 0x1101 /* AL_FORMAT_MONO16 */:
bytes = 2;
channels = 1;
break;
case 0x1102 /* AL_FORMAT_STEREO8 */:
bytes = 1;
channels = 2;
break;
case 0x1103 /* AL_FORMAT_STEREO16 */:
bytes = 2;
channels = 2;
break;
case 0x10010 /* AL_FORMAT_MONO_FLOAT32 */:
bytes = 4;
channels = 1;
break;
case 0x10011 /* AL_FORMAT_STEREO_FLOAT32 */:
bytes = 4;
channels = 2;
break;
default:
#if OPENAL_DEBUG
console.error("alBufferData called with invalid format " + format);
#endif
return;
}
try {
AL.currentContext.buf[buffer - 1] = AL.currentContext.ctx.createBuffer(channels, size / (bytes * channels), freq);
AL.currentContext.buf[buffer - 1].bytesPerSample = bytes;
} catch (e) {
AL.currentContext.err = 0xA003 /* AL_INVALID_VALUE */;
return;
}
var buf = new Array(channels);
for (var i = 0; i < channels; ++i) {
buf[i] = AL.currentContext.buf[buffer - 1].getChannelData(i);
}
for (var i = 0; i < size / (bytes * channels); ++i) {
for (var j = 0; j < channels; ++j) {
switch (bytes) {
case 1:
var val = {{{ makeGetValue('data', 'i*channels+j', 'i8') }}} & 0xff; // unsigned
buf[j][i] = -1.0 + val * (2/256);
break;
case 2:
var val = {{{ makeGetValue('data', '2*(i*channels+j)', 'i16') }}};
buf[j][i] = val/32768;
break;
case 4:
buf[j][i] = {{{ makeGetValue('data', '4*(i*channels+j)', 'float') }}};
break;
}
}
}
},
alGetBufferi: function(buffer, param, value)
{
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetBufferi called without a valid context");
#endif
return;
}
var buf = AL.currentContext.buf[buffer - 1];
if (!buf) {
#if OPENAL_DEBUG
console.error("alGetBufferi called with an invalid buffer");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
switch (param) {
case 0x2001 /* AL_FREQUENCY */:
{{{ makeSetValue('value', '0', 'buf.sampleRate', 'i32') }}};
break;
case 0x2002 /* AL_BITS */:
{{{ makeSetValue('value', '0', 'buf.bytesPerSample * 8', 'i32') }}};
break;
case 0x2003 /* AL_CHANNELS */:
{{{ makeSetValue('value', '0', 'buf.numberOfChannels', 'i32') }}};
break;
case 0x2004 /* AL_SIZE */:
{{{ makeSetValue('value', '0', 'buf.length * buf.bytesPerSample * buf.numberOfChannels', 'i32') }}};
break;
default:
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alSourcePlay: function(source) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourcePlay called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourcePlay called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
AL.setSourceState(src, 0x1012 /* AL_PLAYING */);
},
alSourceStop: function(source) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourceStop called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourceStop called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
AL.setSourceState(src, 0x1014 /* AL_STOPPED */);
},
alSourceRewind: function(source) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourceRewind called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourceRewind called with an invalid source");
#endif
AL.currentContext.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 */);
},
alSourcePause: function(source) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alSourcePause called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alSourcePause called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
AL.setSourceState(src, 0x1013 /* AL_PAUSED */);
},
alGetSourcei: function(source, param, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetSourcei called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alGetSourcei called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
// Being that we have no way to receive end events from buffer nodes,
// we currently proccess and update a source's buffer queue every
// ~QUEUE_INTERVAL milliseconds. However, this interval is not precise,
// so we also forcefully update the source when alGetSourcei is queried
// to aid in the common scenario of application calling alGetSourcei(AL_BUFFERS_PROCESSED)
// to recycle buffers.
AL.updateSource(src);
switch (param) {
case 0x202 /* AL_SOURCE_RELATIVE */:
{{{ makeSetValue('value', '0', 'src.panner ? 1 : 0', 'i32') }}};
break;
case 0x1001 /* AL_CONE_INNER_ANGLE */:
{{{ makeSetValue('value', '0', 'src.coneInnerAngle', 'i32') }}};
break;
case 0x1002 /* AL_CONE_OUTER_ANGLE */:
{{{ makeSetValue('value', '0', 'src.coneOuterAngle', 'i32') }}};
break;
case 0x1007 /* AL_LOOPING */:
{{{ makeSetValue('value', '0', 'src.loop', 'i32') }}};
break;
case 0x1009 /* AL_BUFFER */:
if (!src.queue.length) {
{{{ makeSetValue('value', '0', '0', 'i32') }}};
} else {
// Find the first unprocessed buffer.
var buffer = src.queue[src.buffersPlayed].buffer;
// Return its index.
for (var i = 0; i < AL.currentContext.buf.length; ++i) {
if (buffer == AL.currentContext.buf[i]) {
{{{ makeSetValue('value', '0', 'i+1', 'i32') }}};
return;
}
}
{{{ makeSetValue('value', '0', '0', 'i32') }}};
}
break;
case 0x1010 /* AL_SOURCE_STATE */:
{{{ makeSetValue('value', '0', 'src.state', 'i32') }}};
break;
case 0x1015 /* AL_BUFFERS_QUEUED */:
{{{ makeSetValue('value', '0', 'src.queue.length', 'i32') }}}
break;
case 0x1016 /* AL_BUFFERS_PROCESSED */:
if (src.loop) {
{{{ makeSetValue('value', '0', '0', 'i32') }}}
} else {
{{{ makeSetValue('value', '0', 'src.buffersPlayed', 'i32') }}}
}
break;
default:
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alGetSourceiv__deps: ['alGetSourcei'],
alGetSourceiv: function(source, param, values) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetSourceiv called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alGetSourceiv called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
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 */:
_alGetSourcei(source, param, values);
break;
default:
#if OPENAL_DEBUG
console.error("alGetSourceiv with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alGetSourcef: function(source, param, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetSourcef called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alGetSourcef called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
switch (param) {
// case 0x1003 /* AL_PITCH */:
// break;
case 0x100A /* AL_GAIN */:
{{{ makeSetValue('value', '0', 'src.gain.gain.value', 'float') }}}
break;
// case 0x100D /* AL_MIN_GAIN */:
// break;
// case 0x100E /* AL_MAX_GAIN */:
// break;
case 0x1023 /* AL_MAX_DISTANCE */:
{{{ makeSetValue('value', '0', 'src.maxDistance', 'float') }}}
break;
case 0x1021 /* AL_ROLLOFF_FACTOR */:
{{{ makeSetValue('value', '0', 'src.rolloffFactor', 'float') }}}
break;
case 0x1022 /* AL_CONE_OUTER_GAIN */:
{{{ makeSetValue('value', '0', 'src.coneOuterGain', 'float') }}}
break;
case 0x1001 /* AL_CONE_INNER_ANGLE */:
{{{ makeSetValue('value', '0', 'src.coneInnerAngle', 'float') }}}
break;
case 0x1002 /* AL_CONE_OUTER_ANGLE */:
{{{ makeSetValue('value', '0', 'src.coneOuterAngle', 'float') }}}
break;
case 0x1020 /* AL_REFERENCE_DISTANCE */:
{{{ makeSetValue('value', '0', 'src.refDistance', 'float') }}}
break;
// case 0x1024 /* AL_SEC_OFFSET */:
// break;
// case 0x1025 /* AL_SAMPLE_OFFSET */:
// break;
// case 0x1026 /* AL_BYTE_OFFSET */:
// break;
default:
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alGetSourcefv__deps: ['alGetSourcef'],
alGetSourcefv: function(source, param, values) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetSourcefv called without a valid context");
#endif
return;
}
var src = AL.currentContext.src[source];
if (!src) {
#if OPENAL_DEBUG
console.error("alGetSourcefv called with an invalid source");
#endif
AL.currentContext.err = 0xA001 /* AL_INVALID_NAME */;
return;
}
switch (param) {
case 0x1003 /* AL_PITCH */:
case 0x100A /* AL_GAIN */:
case 0x100D /* AL_MIN_GAIN */:
case 0x100E /* AL_MAX_GAIN */:
case 0x1023 /* AL_MAX_DISTANCE */:
case 0x1021 /* AL_ROLLOFF_FACTOR */:
case 0x1022 /* AL_CONE_OUTER_GAIN */:
case 0x1001 /* AL_CONE_INNER_ANGLE */:
case 0x1002 /* AL_CONE_OUTER_ANGLE */:
case 0x1020 /* AL_REFERENCE_DISTANCE */:
case 0x1024 /* AL_SEC_OFFSET */:
case 0x1025 /* AL_SAMPLE_OFFSET */:
case 0x1026 /* AL_BYTE_OFFSET */:
_alGetSourcef(source, param, values);
break;
case 0x1004 /* AL_POSITION */:
var position = src.position;
{{{ makeSetValue('values', '0', 'position[0]', 'float') }}}
{{{ makeSetValue('values', '4', 'position[1]', 'float') }}}
{{{ makeSetValue('values', '8', 'position[2]', 'float') }}}
break;
case 0x1005 /* AL_DIRECTION */:
var direction = src.direction;
{{{ makeSetValue('values', '0', 'direction[0]', 'float') }}}
{{{ makeSetValue('values', '4', 'direction[1]', 'float') }}}
{{{ makeSetValue('values', '8', 'direction[2]', 'float') }}}
break;
case 0x1006 /* AL_VELOCITY */:
var velocity = src.velocity;
{{{ makeSetValue('values', '0', 'velocity[0]', 'float') }}}
{{{ makeSetValue('values', '4', 'velocity[1]', 'float') }}}
{{{ makeSetValue('values', '8', 'velocity[2]', 'float') }}}
break;
default:
#if OPENAL_DEBUG
console.error("alGetSourcefv with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alDistanceModel: function(model) {
if (model !== 0 /* AL_NONE */) {
#if OPENAL_DEBUG
console.log("Only alDistanceModel(AL_NONE) is currently supported");
#endif
}
},
alGetListenerf: function(pname, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetListenerf called without a valid context");
#endif
return;
}
switch (pname) {
case 0x100A /* AL_GAIN */:
{{{ makeSetValue('value', '0', 'AL.currentContext.gain.gain.value', 'float') }}}
break;
default:
#if OPENAL_DEBUG
console.error("alGetListenerf with param " + pname + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alGetListenerfv: function(pname, values) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetListenerfv called without a valid context");
#endif
return;
}
switch (pname) {
case 0x1004 /* AL_POSITION */:
var position = AL.currentContext.ctx.listener._position || [0,0,0];
{{{ makeSetValue('values', '0', 'position[0]', 'float') }}}
{{{ makeSetValue('values', '4', 'position[1]', 'float') }}}
{{{ makeSetValue('values', '8', 'position[2]', 'float') }}}
break;
case 0x1006 /* AL_VELOCITY */:
var velocity = AL.currentContext.ctx.listener._velocity || [0,0,0];
{{{ makeSetValue('values', '0', 'velocity[0]', 'float') }}}
{{{ makeSetValue('values', '4', 'velocity[1]', 'float') }}}
{{{ makeSetValue('values', '8', 'velocity[2]', 'float') }}}
break;
case 0x100F /* AL_ORIENTATION */:
var orientation = AL.currentContext.ctx.listener._orientation || [0,0,0,0,0,0];
{{{ makeSetValue('values', '0', 'orientation[0]', 'float') }}}
{{{ makeSetValue('values', '4', 'orientation[1]', 'float') }}}
{{{ makeSetValue('values', '8', 'orientation[2]', 'float') }}}
{{{ makeSetValue('values', '12', 'orientation[3]', 'float') }}}
{{{ makeSetValue('values', '16', 'orientation[4]', 'float') }}}
{{{ makeSetValue('values', '20', 'orientation[5]', 'float') }}}
break;
default:
#if OPENAL_DEBUG
console.error("alGetListenerfv with param " + pname + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alGetListeneri: function(pname, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alGetListeneri called without a valid context");
#endif
return;
}
switch (pname) {
default:
#if OPENAL_DEBUG
console.error("alGetListeneri with param " + pname + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alListenerf: function(param, value) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alListenerf called without a valid context");
#endif
return;
}
switch (param) {
case 0x100A /* AL_GAIN */:
AL.currentContext.gain.gain.value = value;
break;
default:
#if OPENAL_DEBUG
console.error("alListenerf with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alEnable: function(param) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alEnable called without a valid context");
#endif
return;
}
switch (param) {
default:
#if OPENAL_DEBUG
console.error("alEnable with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alDisable: function(param) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alDisable called without a valid context");
#endif
return;
}
switch (pname) {
default:
#if OPENAL_DEBUG
console.error("alDisable with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alListener3f: function(param, v1, v2, v3) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alListenerfv called without a valid context");
#endif
return;
}
switch (param) {
case 0x1004 /* AL_POSITION */:
AL.currentContext.ctx.listener._position = [v1, v2, v3];
AL.currentContext.ctx.listener.setPosition(v1, v2, v3);
break;
case 0x1006 /* AL_VELOCITY */:
AL.currentContext.ctx.listener._velocity = [v1, v2, v3];
AL.currentContext.ctx.listener.setVelocity(v1, v2, v3);
break;
default:
#if OPENAL_DEBUG
console.error("alListener3f with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alListenerfv: function(param, values) {
if (!AL.currentContext) {
#if OPENAL_DEBUG
console.error("alListenerfv called without a valid context");
#endif
return;
}
switch (param) {
case 0x1004 /* AL_POSITION */:
var x = {{{ makeGetValue('values', '0', 'float') }}};
var y = {{{ makeGetValue('values', '4', 'float') }}};
var z = {{{ makeGetValue('values', '8', 'float') }}};
AL.currentContext.ctx.listener._position = [x, y, z];
AL.currentContext.ctx.listener.setPosition(x, y, z);
break;
case 0x1006 /* AL_VELOCITY */:
var x = {{{ makeGetValue('values', '0', 'float') }}};
var y = {{{ makeGetValue('values', '4', 'float') }}};
var z = {{{ makeGetValue('values', '8', 'float') }}};
AL.currentContext.ctx.listener._velocity = [x, y, z];
AL.currentContext.ctx.listener.setVelocity(x, y, z);
break;
case 0x100F /* AL_ORIENTATION */:
var x = {{{ makeGetValue('values', '0', 'float') }}};
var y = {{{ makeGetValue('values', '4', 'float') }}};
var z = {{{ makeGetValue('values', '8', 'float') }}};
var x2 = {{{ makeGetValue('values', '12', 'float') }}};
var y2 = {{{ makeGetValue('values', '16', 'float') }}};
var z2 = {{{ makeGetValue('values', '20', 'float') }}};
AL.currentContext.ctx.listener._orientation = [x, y, z, x2, y2, z2];
AL.currentContext.ctx.listener.setOrientation(x, y, z, x2, y2, z2);
break;
default:
#if OPENAL_DEBUG
console.error("alListenerfv with param " + param + " not implemented yet");
#endif
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
break;
}
},
alIsExtensionPresent: function(extName) {
extName = Pointer_stringify(extName);
if (extName == "AL_EXT_float32") return 1;
return 0;
},
alcIsExtensionPresent: function(device, extName) {
return 0;
},
alGetString: function(param) {
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 = 'AL_EXT_float32';
break;
default:
AL.currentContext.err = 0xA002 /* AL_INVALID_ENUM */;
return 0;
}
ret = allocate(intArrayFromString(ret), 'i8', ALLOC_NORMAL);
AL.stringCache[param] = ret;
return ret;
},
alGetProcAddress: function(fname) {
return 0;
},
alcGetString: function(device, 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 = 'Device';
} else {
return 0;
}
break;
case 0x1005 /* ALC_DEVICE_SPECIFIER */:
if (typeof(AudioContext) !== "undefined" ||
typeof(webkitAudioContext) !== "undefined") {
ret = 'Device\0';
} else {
ret = '\0';
}
break;
case 0x311 /* ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER */:
return 0;
break;
case 0x310 /* ALC_CAPTURE_DEVICE_SPECIFIER */:
ret = '\0'
break;
case 0x1006 /* ALC_EXTENSIONS */:
if (!device) {
AL.alcErr = 0xA001 /* ALC_INVALID_DEVICE */;
return 0;
}
ret = '';
break;
default:
AL.alcErr = 0xA003 /* ALC_INVALID_ENUM */;
return 0;
}
ret = allocate(intArrayFromString(ret), 'i8', ALLOC_NORMAL);
AL.alcStringCache[param] = ret;
return ret;
},
alcGetProcAddress: function(device, fname) {
return 0;
},
alGetEnumValue: function(name) {
name = Pointer_stringify(name);
if (name == "AL_FORMAT_MONO_FLOAT32") return 0x10010;
if (name == "AL_FORMAT_STEREO_FLOAT32") return 0x10011;
AL.currentContext.err = 0xA003 /* AL_INVALID_VALUE */;
return 0;
},
alSpeedOfSound: function(value) {
Runtime.warnOnce('alSpeedOfSound() is not yet implemented! Ignoring all calls to it.');
},
alDopplerFactor: function(value) {
Runtime.warnOnce('alDopplerFactor() is not yet implemented! Ignoring all calls to it.');
},
alDopplerVelocity: function(value) {
Runtime.warnOnce('alDopplerVelocity() is not yet implemented! Ignoring all calls to it.');
}
};
autoAddDeps(LibraryOpenAL, '$AL');
mergeInto(LibraryManager.library, LibraryOpenAL);