aboutsummaryrefslogtreecommitdiff
path: root/src/library_openal.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/library_openal.js')
-rw-r--r--src/library_openal.js602
1 files changed, 602 insertions, 0 deletions
diff --git a/src/library_openal.js b/src/library_openal.js
new file mode 100644
index 00000000..6a97fce7
--- /dev/null
+++ b/src/library_openal.js
@@ -0,0 +1,602 @@
+//"use strict";
+
+var LibraryOpenAL = {
+ $AL__deps: ['$Browser'],
+ $AL: {
+ contexts: [],
+ currentContext: 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
+ },
+
+ alcCloseDevice: function(device) {
+ // Stop playback, etc
+ },
+
+ alcOpenDevice: function(deviceName) {
+ if (typeof(AudioContext) == "function" ||
+ typeof(webkitAudioContext) == "function") {
+ 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) {
+ AL.contexts.push({ctx: ctx, err: 0, src: [], buf: []});
+ return AL.contexts.length;
+ } else {
+ return 0;
+ }
+ },
+
+ alGetError: function() {
+ if (!AL.currentContext) {
+ return 0xA004 /* AL_INVALID_OPERATION */;
+ } else {
+ return AL.currentContext.err;
+ }
+ },
+
+ alcGetError__deps: ['alGetError'],
+ alcGetError: function(device) {
+ // We have only one audio device, so just return alGetError.
+ return _alGetError();
+ },
+
+ 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') }}} - 1;
+ 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();
+ var panner = AL.currentContext.ctx.createPanner();
+ panner.panningModel = "equalpower";
+ panner.distanceModel = "linear";
+ panner.rolloffFactor = 0.3;
+ gain.connect(panner);
+ panner.connect(AL.currentContext.ctx.destination);
+ AL.currentContext.src.push({
+ loop: false,
+ buffer: null,
+ gain: gain,
+ panner: panner,
+ paused: false,
+ playTime: -1,
+ pausedTime: 0
+ });
+ {{{ makeSetValue('sources', 'i*4', 'AL.currentContext.src.length', 'i32') }}};
+ }
+ },
+
+ alSourcei: function(source, param, value) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourcei called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourcei called with an invalid source");
+#endif
+ return;
+ }
+ switch (param) {
+ case 0x1007 /* AL_LOOPING */:
+ AL.currentContext.src[source - 1].loop = (value != 0 /* AL_FALSE */);
+ break;
+ case 0x1009 /* AL_BUFFER */:
+ if (value == 0) {
+ AL.currentContext.src[source - 1].buffer = null;
+ } else {
+ AL.currentContext.src[source - 1].buffer = AL.currentContext.buf[value - 1].buf;
+ }
+ break;
+ default:
+#if OPENAL_DEBUG
+ console.log("alSourcei with param " + param + " not implemented yet");
+#endif
+ break;
+ }
+ },
+
+ alSourcef: function(source, param, value) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourcef called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourcef called with an invalid source");
+#endif
+ return;
+ }
+ switch (param) {
+ case 0x100A /* AL_GAIN */:
+ if (AL.currentContext.src[source - 1]) {
+ AL.currentContext.src[source - 1].gain.gain.value = value;
+ }
+ break;
+ case 0x1003 /* AL_PITCH */:
+#if OPENAL_DEBUG
+ console.log("alSourcef was called with AL_PITCH, but Web Audio does not support static pitch changes");
+#endif
+ break;
+ default:
+#if OPENAL_DEBUG
+ console.log("alSourcef with param " + param + " not implemented yet");
+#endif
+ break;
+ }
+ },
+
+ alSourcefv: function(source, param, value) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourcefv called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourcefv called with an invalid source");
+#endif
+ return;
+ }
+ switch (param) {
+ case 0x1004 /* AL_POSITION */:
+ AL.currentContext.src[source - 1].panner.setPosition(
+ {{{ makeGetValue('value', '0', 'float') }}},
+ {{{ makeGetValue('value', '4', 'float') }}},
+ {{{ makeGetValue('value', '8', 'float') }}}
+ );
+ break;
+ case 0x1006 /* AL_VELOCITY */:
+ AL.currentContext.src[source - 1].panner.setVelocity(
+ {{{ makeGetValue('value', '0', 'float') }}},
+ {{{ makeGetValue('value', '4', 'float') }}},
+ {{{ makeGetValue('value', '8', 'float') }}}
+ );
+ break;
+ default:
+#if OPENAL_DEBUG
+ console.log("alSourcefv with param " + param + " not implemented yet");
+#endif
+ break;
+ }
+ },
+
+ alSourceQueueBuffers: function(source, count, buffers) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourceQueueBuffers called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourceQueueBuffers called with an invalid source");
+#endif
+ return;
+ }
+ if (count != 1) {
+#if OPENAL_DEBUG
+ console.error("Queuing multiple buffers using alSourceQueueBuffers is not supported yet");
+#endif
+ return;
+ }
+ for (var i = 0; i < count; ++i) {
+ var buffer = {{{ makeGetValue('buffers', 'i*4', 'i32') }}};
+ if (buffer > AL.currentContext.buf.length) {
+#if OPENAL_DEBUG
+ console.error("alSourceQueueBuffers called with an invalid buffer");
+#endif
+ return;
+ }
+ AL.currentContext.src[source - 1].buffer = AL.currentContext.buf[buffer - 1].buf;
+ }
+ },
+
+ alSourceUnqueueBuffers: function(source, count, buffers)
+ {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourceUnqueueBuffers called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourceUnqueueBuffers called with an invalid source");
+#endif
+ return;
+ }
+ if (count != 1) {
+#if OPENAL_DEBUG
+ console.error("Queuing multiple buffers using alSourceUnqueueBuffers is not supported yet");
+#endif
+ return;
+ }
+ for (var i = 0; i < count; ++i) {
+ var buffer = AL.currentContext.src[source - 1].buffer;
+ for (var j = 0; j < AL.currentContext.buf.length; ++j) {
+ if (buffer == AL.currentContext.buf[j].buf) {
+ {{{ makeSetValue('buffers', 'i*4', 'j+1', 'i32') }}};
+ AL.currentContext.src[source - 1].buffer = null;
+ break;
+ }
+ }
+ }
+ },
+
+ alDeleteBuffers: function(count, buffers)
+ {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alDeleteBuffers called without a valid context");
+#endif
+ return;
+ }
+ for (var i = 0; i < count; ++i) {
+ var bufferIdx = {{{ makeGetValue('buffers', 'i*4', 'i32') }}} - 1;
+ if (bufferIdx < AL.currentContext.buf.length && AL.currentContext.buf[bufferIdx]) {
+ var buffer = AL.currentContext.buf[bufferIdx].buf;
+ for (var j = 0; j < AL.currentContext.src.length; ++j) {
+ if (buffer == AL.currentContext.src[j].buffer) {
+ AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */;
+ return;
+ }
+ }
+ 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({buf: null});
+ {{{ makeSetValue('buffers', 'i*4', 'AL.currentContext.buf.length', 'i32') }}};
+ }
+ },
+
+ 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;
+ default:
+#if OPENAL_DEBUG
+ console.error("alBufferData called with invalid format " + format);
+#endif
+ return;
+ }
+ AL.currentContext.buf[buffer - 1].buf = AL.currentContext.ctx.createBuffer(channels, size / (bytes * channels), freq);
+ var buf = new Array(channels);
+ for (var i = 0; i < channels; ++i) {
+ buf[i] = AL.currentContext.buf[buffer - 1].buf.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') }}};
+ 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;
+ }
+ }
+ }
+ },
+
+ alSourcePlay__deps: ["alSourceStop"],
+ alSourcePlay: function(source) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourcePlay called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourcePlay called with an invalid source");
+#endif
+ return;
+ }
+ var offset = 0;
+ if ("src" in AL.currentContext.src[source - 1] &&
+ AL.currentContext.src[source - 1]["src"].buffer ==
+ AL.currentContext.src[source - 1].buffer) {
+ if (AL.currentContext.src[source - 1].paused) {
+ // So now we have to resume playback, remember the offset here.
+ offset = AL.currentContext.src[source - 1].pausedTime -
+ AL.currentContext.src[source - 1].playTime;
+ } else {
+ // If the source is already playing, we need to resume from beginning.
+ // We do that by stopping the current source and replaying it.
+ _alSourceStop(source);
+ }
+ }
+ var src = AL.currentContext.ctx.createBufferSource();
+ src.loop = AL.currentContext.src[source - 1].loop;
+ src.buffer = AL.currentContext.src[source - 1].buffer;
+ src.connect(AL.currentContext.src[source - 1].gain);
+ src.start(0, offset);
+ AL.currentContext.src[source - 1].playTime = AL.currentContext.ctx.currentTime;
+ AL.currentContext.src[source - 1].paused = false;
+ AL.currentContext.src[source - 1]['src'] = src;
+ },
+
+ alSourceStop: function(source) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourceStop called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourceStop called with an invalid source");
+#endif
+ return;
+ }
+ if (AL.currentContext.src[source - 1] && "src" in AL.currentContext.src[source - 1]) {
+ AL.currentContext.src[source - 1]["src"].stop(0);
+ delete AL.currentContext.src[source - 1]["src"];
+ }
+ },
+
+ alSourcePause: function(source) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alSourcePause called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alSourcePause called with an invalid source");
+#endif
+ return;
+ }
+ if ("src" in AL.currentContext.src[source - 1] &&
+ !AL.currentContext.src[source - 1].paused) {
+ AL.currentContext.src[source - 1].paused = true;
+ AL.currentContext.src[source - 1].pausedTime = AL.currentContext.ctx.currentTime;
+ AL.currentContext.src[source - 1]["src"].stop(0);
+ delete AL.currentContext.src[source - 1].src;
+ }
+ },
+
+ alGetSourcei: function(source, param, value) {
+ if (!AL.currentContext) {
+#if OPENAL_DEBUG
+ console.error("alGetSourcei called without a valid context");
+#endif
+ return;
+ }
+ if (source > AL.currentContext.src.length) {
+#if OPENAL_DEBUG
+ console.error("alGetSourcei called with an invalid source");
+#endif
+ return;
+ }
+ switch (param) {
+ case 0x202 /* AL_SOURCE_RELATIVE */:
+ // Always return 1
+ {{{ makeSetValue('value', '0', '1', 'i32') }}};
+ break;
+ case 0x1009 /* AL_BUFFER */:
+ if (AL.currentContext.src[source - 1].buffer == null) {
+ {{{ makeSetValue('value', '0', '0', 'i32') }}};
+ } else {
+ var buf = AL.currentContext.src[source - 1].buffer;
+ for (var i = 0; i < AL.currentContext.buf.length; ++i) {
+ if (buf == AL.currentContext.buf[i].buf) {
+ {{{ makeSetValue('value', '0', 'i+1', 'i32') }}};
+ return;
+ }
+ }
+ {{{ makeSetValue('value', '0', '0', 'i32') }}};
+ }
+ break;
+ case 0x1010 /* AL_SOURCE_STATE */:
+ if ("src" in AL.currentContext.src[source - 1]) {
+ {{{ makeSetValue('value', '0', '0x1012', 'i32') }}} /* AL_PLAYING */;
+ } else if (AL.currentContext.src[source - 1].paused) {
+ {{{ makeSetValue('value', '0', '0x1013', 'i32') }}} /* AL_PAUSED */;
+ } else if (AL.currentContext.src[source - 1].playTime == -1) {
+ {{{ makeSetValue('value', '0', '0x1011', 'i32') }}} /* AL_INITIAL */;
+ } else {
+ {{{ makeSetValue('value', '0', '0x1014', 'i32') }}} /* AL_STOPPED */;
+ }
+ break;
+ case 0x1015 /* AL_BUFFERS_QUEUED */:
+ if (AL.currentContext.src[source - 1].buffer) {
+ {{{ makeSetValue('value', '0', '1', 'i32') }}}
+ } else {
+ {{{ makeSetValue('value', '0', '0', 'i32') }}}
+ }
+ break;
+ case 0x1016 /* AL_BUFFERS_PROCESSED */:
+ // Always return 1
+ {{{ makeSetValue('value', '0', '1', 'i32') }}}
+ break;
+ }
+ },
+
+ alDistanceModel: function(model) {
+ if (model != 0 /* AL_NONE */) {
+#if OPENAL_DEBUG
+ console.log("Only alDistanceModel(AL_NONE) is currently supported");
+#endif
+ }
+ },
+
+ 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 */:
+ AL.currentContext.ctx.listener.setPosition(
+ {{{ makeGetValue('values', '0', 'float') }}},
+ {{{ makeGetValue('values', '4', 'float') }}},
+ {{{ makeGetValue('values', '8', 'float') }}}
+ );
+ break;
+ case 0x1006 /* AL_VELOCITY */:
+ AL.currentContext.ctx.listener.setVelocity(
+ {{{ makeGetValue('values', '0', 'float') }}},
+ {{{ makeGetValue('values', '4', 'float') }}},
+ {{{ makeGetValue('values', '8', 'float') }}}
+ );
+ break;
+ case 0x100F /* AL_ORIENTATION */:
+ AL.currentContext.ctx.listener.setOrientation(
+ {{{ makeGetValue('values', '0', 'float') }}},
+ {{{ makeGetValue('values', '4', 'float') }}},
+ {{{ makeGetValue('values', '8', 'float') }}},
+ {{{ makeGetValue('values', '12', 'float') }}},
+ {{{ makeGetValue('values', '16', 'float') }}},
+ {{{ makeGetValue('values', '20', 'float') }}}
+ );
+ break;
+ default:
+#if OPENAL_DEBUG
+ console.log("alListenerfv with param " + param + " not implemented yet");
+#endif
+ break;
+ }
+ },
+
+ alIsExtensionPresent: function(extName) {
+ return 0;
+ },
+
+ alcIsExtensionPresent: function(device, extName) {
+ return 0;
+ },
+
+ alGetProcAddress: function(fname) {
+ return 0;
+ },
+
+ alcGetProcAddress: function(device, fname) {
+ return 0;
+ },
+};
+
+autoAddDeps(LibraryOpenAL, '$AL');
+mergeInto(LibraryManager.library, LibraryOpenAL);
+