aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/embind/emval.js5
-rw-r--r--src/library_browser.js42
-rw-r--r--src/library_html5.js6
-rw-r--r--src/library_sdl.js372
-rw-r--r--src/preamble.js188
-rw-r--r--src/relooper/Relooper.cpp9
-rw-r--r--src/relooper/fuzzer.py6
-rw-r--r--src/relooper/test.cpp31
-rw-r--r--src/relooper/test.txt153
-rw-r--r--src/runtime.js9
-rw-r--r--src/shell.html11
-rw-r--r--src/shell_minimal.html11
-rw-r--r--src/struct_info.json18
13 files changed, 651 insertions, 210 deletions
diff --git a/src/embind/emval.js b/src/embind/emval.js
index 4007701a..ebec3881 100644
--- a/src/embind/emval.js
+++ b/src/embind/emval.js
@@ -286,3 +286,8 @@ function __emval_has_function(handle, name) {
name = getStringOrSymbol(name);
return handle[name] instanceof Function;
}
+
+function __emval_typeof(handle) {
+ handle = requireHandle(handle);
+ return __emval_register(typeof handle);
+}
diff --git a/src/library_browser.js b/src/library_browser.js
index 44e8c473..4ef7c577 100644
--- a/src/library_browser.js
+++ b/src/library_browser.js
@@ -322,11 +322,6 @@ mergeInto(LibraryManager.library, {
#endif
// Set the background of the WebGL canvas to black
canvas.style.backgroundColor = "black";
-
- // Warn on context loss
- canvas.addEventListener('webglcontextlost', function(event) {
- alert('WebGL context lost. You will need to reload the page.');
- }, false);
}
if (setInModule) {
GLctx = Module.ctx = ctx;
@@ -478,7 +473,21 @@ mergeInto(LibraryManager.library, {
},
getMouseWheelDelta: function(event) {
- return Math.max(-1, Math.min(1, event.type === 'DOMMouseScroll' ? event.detail : -event.wheelDelta));
+ var delta = 0;
+ switch (event.type) {
+ case 'DOMMouseScroll':
+ delta = event.detail;
+ break;
+ case 'mousewheel':
+ delta = -event.wheelDelta;
+ break;
+ case 'wheel':
+ delta = event.deltaY;
+ break;
+ default:
+ throw 'unrecognized mouse wheel event: ' + event.type;
+ }
+ return Math.max(-1, Math.min(1, delta));
},
mouseX: 0,
@@ -688,15 +697,22 @@ mergeInto(LibraryManager.library, {
emscripten_async_wget: function(url, file, onload, onerror) {
var _url = Pointer_stringify(url);
var _file = Pointer_stringify(file);
+ function doCallback(callback) {
+ if (callback) {
+ var stack = Runtime.stackSave();
+ Runtime.dynCall('vi', callback, [allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]);
+ Runtime.stackRestore(stack);
+ }
+ }
FS.createPreloadedFile(
PATH.dirname(_file),
PATH.basename(_file),
_url, true, true,
function() {
- if (onload) Runtime.dynCall('vi', onload, [file]);
+ doCallback(onload);
},
function() {
- if (onerror) Runtime.dynCall('vi', onerror, [file]);
+ doCallback(onerror);
}
);
},
@@ -727,7 +743,11 @@ mergeInto(LibraryManager.library, {
http.onload = function http_onload(e) {
if (http.status == 200) {
FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true);
- if (onload) Runtime.dynCall('vii', onload, [arg, file]);
+ if (onload) {
+ var stack = Runtime.stackSave();
+ Runtime.dynCall('vii', onload, [arg, allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]);
+ Runtime.stackRestore(stack);
+ }
} else {
if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]);
}
@@ -740,8 +760,8 @@ mergeInto(LibraryManager.library, {
// PROGRESS
http.onprogress = function http_onprogress(e) {
- if (e.lengthComputable || (e.lengthComputable === undefined && e.totalSize != 0)) {
- var percentComplete = (e.position / e.totalSize)*100;
+ if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) {
+ var percentComplete = (e.loaded / e.total)*100;
if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]);
}
};
diff --git a/src/library_html5.js b/src/library_html5.js
index d9376c2a..d5d0cd66 100644
--- a/src/library_html5.js
+++ b/src/library_html5.js
@@ -1307,6 +1307,12 @@ var LibraryJSEvents = {
JSEvents.registerWebGlEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED') }}}, "webglcontextrestored");
return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}};
},
+
+ emscripten_is_webgl_context_lost: function(target) {
+ // TODO: In the future if multiple GL contexts are supported, use the 'target' parameter to find the canvas to query.
+ if (!Module['ctx']) return true; // No context ~> lost context.
+ return Module['ctx'].isContextLost();
+ }
};
autoAddDeps(LibraryJSEvents, '$JSEvents');
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 077f72eb..d9639907 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -430,6 +430,15 @@ var LibrarySDL = {
savedKeydown: null,
receiveEvent: function(event) {
+ function unpressAllPressedKeys() {
+ // Un-press all pressed keys: TODO
+ for (var code in SDL.keyboardMap) {
+ SDL.events.push({
+ type: 'keyup',
+ keyCode: SDL.keyboardMap[code]
+ });
+ }
+ };
switch(event.type) {
case 'touchstart': case 'touchmove': {
event.preventDefault();
@@ -532,7 +541,7 @@ var LibrarySDL = {
}
}
// fall through
- case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel':
+ case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': case 'wheel':
// If we preventDefault on keydown events, the subsequent keypress events
// won't fire. However, it's fine (and in some cases necessary) to
// preventDefault for keys that don't generate a character. Otherwise,
@@ -541,21 +550,40 @@ var LibrarySDL = {
event.preventDefault();
}
- if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel') {
+ if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel' || event.type == 'wheel') {
+ // Simulate old-style SDL events representing mouse wheel input as buttons
var button = Browser.getMouseWheelDelta(event) > 0 ? 4 : 3;
- var event2 = {
+ var event1 = {
type: 'mousedown',
button: button,
pageX: event.pageX,
pageY: event.pageY
};
- SDL.events.push(event2);
- event = {
+ SDL.events.push(event1);
+ var event2 = {
type: 'mouseup',
button: button,
pageX: event.pageX,
pageY: event.pageY
};
+ SDL.events.push(event2);
+
+ // Convert DOMMouseScroll events to wheel events for new style SDL events.
+ if (event.type == 'DOMMouseScroll') {
+ SDL.events.push({
+ type: 'wheel',
+ deltaX: 0,
+ deltaY: -event.detail,
+ });
+ break;
+ } else if (event.type == 'mousewheel') {
+ SDL.events.push({
+ type: 'wheel',
+ deltaX: 0,
+ deltaY: event.wheelDelta,
+ });
+ break;
+ }
} else if (event.type == 'mousedown') {
SDL.DOMButtons[event.button] = 1;
SDL.events.push({
@@ -635,18 +663,23 @@ var LibrarySDL = {
}
event.preventDefault();
break;
+ case 'focus':
+ SDL.events.push(event);
+ event.preventDefault();
+ break;
case 'blur':
- case 'visibilitychange': {
- // Un-press all pressed keys: TODO
- for (var code in SDL.keyboardMap) {
- SDL.events.push({
- type: 'keyup',
- keyCode: SDL.keyboardMap[code]
- });
- }
+ SDL.events.push(event);
+ unpressAllPressedKeys();
+ event.preventDefault();
+ break;
+ case 'visibilitychange':
+ SDL.events.push({
+ type: 'visibilitychange',
+ visible: !document.hidden
+ });
+ unpressAllPressedKeys();
event.preventDefault();
break;
- }
case 'unload':
if (Browser.mainLoop.runner) {
SDL.events.push(event);
@@ -787,8 +820,15 @@ var LibrarySDL = {
}
break;
}
+ case 'wheel': {
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.x, 'event.deltaX', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.y, 'event.deltaY', 'i32') }}};
+ break;
+ }
case 'touchstart': case 'touchend': case 'touchmove': {
var touch = event.touch;
+ if (!Browser.touches[touch.identifier]) break;
var w = Module['canvas'].width;
var h = Module['canvas'].height;
var x = Browser.touches[touch.identifier].x / w;
@@ -839,6 +879,29 @@ var LibrarySDL = {
{{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}};
break;
}
+ case 'focus': {
+ var SDL_WINDOWEVENT_FOCUS_GAINED = 12 /* SDL_WINDOWEVENT_FOCUS_GAINED */;
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_GAINED', 'i8') }}};
+ break;
+ }
+ case 'blur': {
+ var SDL_WINDOWEVENT_FOCUS_LOST = 13 /* SDL_WINDOWEVENT_FOCUS_LOST */;
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_LOST', 'i8') }}};
+ break;
+ }
+ case 'visibilitychange': {
+ var SDL_WINDOWEVENT_SHOWN = 1 /* SDL_WINDOWEVENT_SHOWN */;
+ var SDL_WINDOWEVENT_HIDDEN = 2 /* SDL_WINDOWEVENT_HIDDEN */;
+ var visibilityEventID = event.visible ? SDL_WINDOWEVENT_SHOWN : SDL_WINDOWEVENT_HIDDEN;
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}};
+ {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}};
+ break;
+ }
default: throw 'Unhandled SDL event: ' + event.type;
}
},
@@ -880,11 +943,67 @@ var LibrarySDL = {
var ret = info.volume * 128; // MIX_MAX_VOLUME
if (volume != -1) {
info.volume = volume / 128;
- if (info.audio) info.audio.volume = info.volume;
+ if (info.audio) {
+ info.audio.volume = info.volume; // For <audio> element
+ if (info.audio.webAudioGainNode) info.audio.webAudioGainNode['gain']['value'] = info.volume; // For WebAudio playback
+ }
}
return ret;
},
+ // Plays out an SDL audio resource that was loaded with the Mix_Load APIs, when using Web Audio..
+ playWebAudio: function(audio) {
+ if (!audio) return;
+ if (audio.webAudioNode) return; // This instance is already playing, don't start again.
+ if (!SDL.webAudioAvailable()) return;
+ var webAudio = audio.resource.webAudio;
+ audio.paused = false;
+ if (!webAudio.decodedBuffer) {
+ if (webAudio.onDecodeComplete === undefined) abort("Cannot play back audio object that was not loaded");
+ webAudio.onDecodeComplete.push(function() { if (!audio.paused) SDL.playWebAudio(audio); });
+ return;
+ }
+ audio.webAudioNode = SDL.audioContext['createBufferSource']();
+ audio.webAudioNode['buffer'] = webAudio.decodedBuffer;
+ audio.webAudioNode['loop'] = audio.loop;
+ audio.webAudioNode['onended'] = function() { audio.onended(); } // For <media> element compatibility, route the onended signal to the instance.
+
+ // Add an intermediate gain node to control volume.
+ audio.webAudioGainNode = SDL.audioContext['createGain']();
+ audio.webAudioGainNode['gain']['value'] = audio.volume;
+ audio.webAudioNode['connect'](audio.webAudioGainNode);
+ audio.webAudioGainNode['connect'](SDL.audioContext['destination']);
+ audio.webAudioNode['start'](0, audio.currentPosition);
+ audio.startTime = SDL.audioContext['currentTime'] - audio.currentPosition;
+ },
+
+ // Pausea an SDL audio resource that was played with Web Audio..
+ pauseWebAudio: function(audio) {
+ if (!audio) return;
+ if (audio.webAudioNode) {
+ // Remember where we left off, so that if/when we resume, we can restart the playback at a proper place.
+ audio.currentPosition = (SDL.audioContext['currentTime'] - audio.startTime) % audio.resource.webAudio.decodedBuffer.duration;
+ // Important: When we reach here, the audio playback is stopped by the user. But when calling .stop() below, the Web Audio
+ // graph will send the onended signal, but we don't want to process that, since pausing should not clear/destroy the audio
+ // channel.
+ audio.webAudioNode['onended'] = undefined;
+ audio.webAudioNode.stop();
+ audio.webAudioNode = undefined;
+ }
+ audio.paused = true;
+ },
+
+ openAudioContext: function() {
+ // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page,
+ // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'.
+ if (!SDL.audioContext) {
+ if (typeof(AudioContext) !== 'undefined') SDL.audioContext = new AudioContext();
+ else if (typeof(webkitAudioContext) !== 'undefined') SDL.audioContext = new webkitAudioContext();
+ }
+ },
+
+ webAudioAvailable: function() { return !!SDL.audioContext; },
+
fillWebAudioBufferFromHeap: function(heapPtr, sizeSamplesPerChannel, dstAudioBuffer) {
// The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit or 16-bit as
// supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data,
@@ -1042,6 +1161,7 @@ var LibrarySDL = {
document.addEventListener("keydown", SDL.receiveEvent);
document.addEventListener("keyup", SDL.receiveEvent);
document.addEventListener("keypress", SDL.receiveEvent);
+ window.addEventListener("focus", SDL.receiveEvent);
window.addEventListener("blur", SDL.receiveEvent);
document.addEventListener("visibilitychange", SDL.receiveEvent);
}
@@ -1064,11 +1184,16 @@ var LibrarySDL = {
SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */;
SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */;
SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */;
+ SDL.DOMEventToSDLEvent['wheel'] = 0x403 /* SDL_MOUSEWHEEL */;
SDL.DOMEventToSDLEvent['touchstart'] = 0x700 /* SDL_FINGERDOWN */;
SDL.DOMEventToSDLEvent['touchend'] = 0x701 /* SDL_FINGERUP */;
SDL.DOMEventToSDLEvent['touchmove'] = 0x702 /* SDL_FINGERMOTION */;
SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */;
SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */;
+ SDL.DOMEventToSDLEvent['visibilitychange'] = 0x200 /* SDL_WINDOWEVENT */;
+ SDL.DOMEventToSDLEvent['focus'] = 0x200 /* SDL_WINDOWEVENT */;
+ SDL.DOMEventToSDLEvent['blur'] = 0x200 /* SDL_WINDOWEVENT */;
+
// These are not technically DOM events; the HTML gamepad API is poll-based.
// However, we define them here, as the rest of the SDL code assumes that
// all SDL events originate as DOM events.
@@ -1138,7 +1263,7 @@ var LibrarySDL = {
},
SDL_SetVideoMode: function(width, height, depth, flags) {
- ['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) {
+ ['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'wheel', 'mouseout'].forEach(function(event) {
Module['canvas'].addEventListener(event, SDL.receiveEvent, true);
});
@@ -1181,11 +1306,11 @@ var LibrarySDL = {
for (var i = 0; i < SDL.numChannels; ++i) {
if (SDL.channels[i].audio) {
SDL.channels[i].audio.pause();
+ SDL.channels[i].audio = undefined;
}
}
- if (SDL.music.audio) {
- SDL.music.audio.pause();
- }
+ if (SDL.music.audio) SDL.music.audio.pause();
+ SDL.music.audio = undefined;
Module.print('SDL_Quit called (and ignored)');
},
@@ -1935,15 +2060,8 @@ var LibrarySDL = {
} else {
// Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page,
// since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'.
- if (!SDL.audioContext) {
- if (typeof(AudioContext) !== 'undefined') {
- SDL.audioContext = new AudioContext();
- } else if (typeof(webkitAudioContext) !== 'undefined') {
- SDL.audioContext = new webkitAudioContext();
- } else {
- throw 'Web Audio API is not available!';
- }
- }
+ SDL.openAudioContext();
+ if (!SDL.audioContext) throw 'Web Audio API is not available!';
SDL.audio.soundSource = new Array(); // Use an array of sound sources as a ring buffer to queue blocks of synthesized audio to Web Audio API.
SDL.audio.nextSoundSource = 0; // Index of the next sound buffer in the ring buffer queue to play.
SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start.
@@ -2126,6 +2244,7 @@ var LibrarySDL = {
Mix_Quit: function(){},
Mix_OpenAudio: function(frequency, format, channels, chunksize) {
+ SDL.openAudioContext();
SDL.allocateChannels(32);
// Just record the values for a later call to Mix_QuickLoad_RAW
SDL.mixerFrequency = frequency;
@@ -2168,6 +2287,7 @@ var LibrarySDL = {
var filename = '';
var audio;
+ var webAudio;
var bytes;
if (rwops.filename !== undefined) {
@@ -2175,7 +2295,7 @@ var LibrarySDL = {
var raw = Module["preloadedAudios"][filename];
if (!raw) {
if (raw === null) Module.printErr('Trying to reuse preloaded audio, but freePreloadedMediaOnUse is set!');
- Runtime.warnOnce('Cannot find preloaded audio ' + filename);
+ if (!Module.noAudioDecoding) Runtime.warnOnce('Cannot find preloaded audio ' + filename);
// see if we can read the file-contents from the in-memory FS
try {
@@ -2191,45 +2311,77 @@ var LibrarySDL = {
audio = raw;
}
else if (rwops.bytes !== undefined) {
- bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count);
+ // For Web Audio context buffer decoding, we must make a clone of the audio data, but for <media> element,
+ // a view to existing data is sufficient.
+ if (SDL.webAudioAvailable()) bytes = HEAPU8.buffer.slice(rwops.bytes, rwops.bytes + rwops.count);
+ else bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count);
}
else {
return 0;
}
- // Here, we didn't find a preloaded audio but we either were passed a filepath for
- // which we loaded bytes, or we were passed some bytes
- if (audio === undefined && bytes) {
+ if (bytes !== undefined && SDL.webAudioAvailable()) {
+ audio = undefined;
+ webAudio = {};
+ // The audio decoding process is asynchronous, which gives trouble if user code plays the audio data back immediately
+ // after loading. Therefore prepare an array of callback handlers to run when this audio decoding is complete, which
+ // will then start the playback (with some delay).
+ webAudio.onDecodeComplete = []; // While this member array exists, decoding hasn't finished yet.
+ function onDecodeComplete(data) {
+ webAudio.decodedBuffer = data;
+ // Call all handlers that were waiting for this decode to finish, and clear the handler list.
+ webAudio.onDecodeComplete.forEach(function(e) { e(); });
+ webAudio.onDecodeComplete = undefined; // Don't allow more callback handlers since audio has finished decoding.
+ }
+
+ SDL.audioContext['decodeAudioData'](bytes.buffer || bytes, onDecodeComplete);
+ } else if (audio === undefined && bytes) {
+ // Here, we didn't find a preloaded audio but we either were passed a filepath for
+ // which we loaded bytes, or we were passed some bytes
var blob = new Blob([bytes], {type: rwops.mimetype});
var url = URL.createObjectURL(blob);
audio = new Audio();
audio.src = url;
+ audio.mozAudioChannelType = 'content'; // bugzilla 910340
}
var id = SDL.audios.length;
// Keep the loaded audio in the audio arrays, ready for playback
SDL.audios.push({
source: filename,
- audio: audio
+ audio: audio, // Points to the <audio> element, if loaded
+ webAudio: webAudio // Points to a Web Audio -specific resource object, if loaded
});
return id;
},
Mix_QuickLoad_RAW: function(mem, len) {
- var audio = new Audio();
- // Record the number of channels and frequency for later usage
- audio.numChannels = SDL.mixerNumChannels;
- audio.frequency = SDL.mixerFrequency;
+ var audio;
+ var webAudio;
+
var numSamples = len >> 1; // len is the length in bytes, and the array contains 16-bit PCM values
var buffer = new Float32Array(numSamples);
for (var i = 0; i < numSamples; ++i) {
buffer[i] = ({{{ makeGetValue('mem', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?)
}
- // FIXME: doesn't make sense to keep the audio element in the buffer
+
+ if (SDL.webAudioAvailable()) {
+ webAudio = {};
+ webAudio.decodedBuffer = buffer;
+ } else {
+ var audio = new Audio();
+ audio.mozAudioChannelType = 'content'; // bugzilla 910340
+ // Record the number of channels and frequency for later usage
+ audio.numChannels = SDL.mixerNumChannels;
+ audio.frequency = SDL.mixerFrequency;
+ // FIXME: doesn't make sense to keep the audio element in the buffer
+ }
+
var id = SDL.audios.length;
SDL.audios.push({
source: '',
audio: audio,
+ webAudio: webAudio,
buffer: buffer
});
return id;
@@ -2242,13 +2394,12 @@ var LibrarySDL = {
SDL.channelMinimumNumber = num;
},
Mix_PlayChannel: function(channel, id, loops) {
- // TODO: handle loops
+ // TODO: handle fixed amount of N loops. Currently loops either 0 or infinite times.
// Get the audio element associated with the ID
var info = SDL.audios[id];
if (!info) return -1;
- var audio = info.audio;
- if (!audio) return -1;
+ if (!info.audio && !info.webAudio) return -1;
// If the user asks us to allocate a channel automatically, get the first
// free one.
@@ -2264,73 +2415,33 @@ var LibrarySDL = {
return -1;
}
}
- // We clone the audio node to utilize the preloaded audio buffer, since
- // the browser has already preloaded the audio file.
var channelInfo = SDL.channels[channel];
- channelInfo.audio = audio = audio.cloneNode(true);
- audio.numChannels = info.audio.numChannels;
- audio.frequency = info.audio.frequency;
- // TODO: handle N loops. Behavior matches Mix_PlayMusic
- audio.loop = loops != 0;
- audio['onended'] = function SDL_audio_onended() { // TODO: cache these
- channelInfo.audio = null;
- if (SDL.channelFinished) {
- Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel);
- }
- }
- // Either play the element, or load the dynamic data into it
- if (info.buffer) {
- var contextCtor = null;
- if (audio && ('mozSetup' in audio)) { // Audio Data API
- try {
- audio['mozSetup'](audio.numChannels, audio.frequency);
- audio["mozWriteAudio"](info.buffer);
- } catch (e) {
- // Workaround for Firefox bug 783052
- // ignore this exception!
- }
- /*
- } else if (contextCtor = (window.AudioContext || // WebAudio API
- window.webkitAudioContext)) {
- var currentIndex = 0;
- var numChannels = parseInt(audio.numChannels);
- var context = new contextCtor();
- var source = context.createBufferSource();
- source.loop = false;
- source.buffer = context.createBuffer(numChannels, 1, audio.frequency);
- var jsNode = context.createJavaScriptNode(2048, numChannels, numChannels);
- jsNode.onaudioprocess = function jsNode_onaudioprocess(event) {
- var buffers = new Array(numChannels);
- for (var i = 0; i < numChannels; ++i) {
- buffers[i] = event.outputBuffer.getChannelData(i);
- }
- var remaining = info.buffer.length - currentIndex;
- if (remaining > 2048) {
- remaining = 2048;
- }
- for (var i = 0; i < remaining;) {
- for (var j = 0; j < numChannels; ++j) {
- buffers[j][i] = info.buffer[currentIndex + i + j] * audio.volume;
- }
- i += j;
- }
- currentIndex += remaining * numChannels;
- for (var i = remaining; i < 2048;) {
- for (var j = 0; j < numChannels; ++j) {
- buffers[j][i] = 0; // silence
- }
- i += j;
- }
- };
- source.connect(jsNode);
- jsNode.connect(context.destination);
- source.noteOn(0);
- */
- }
+ var audio;
+ if (info.webAudio) {
+ // Create an instance of the WebAudio object.
+ audio = {};
+ audio.resource = info; // This new object is an instance that refers to this existing resource.
+ audio.paused = false;
+ audio.currentPosition = 0;
+ // Make our instance look similar to the instance of a <media> to make api simple.
+ audio.play = function() { SDL.playWebAudio(this); }
+ audio.pause = function() { SDL.pauseWebAudio(this); }
} else {
- audio.play();
+ // We clone the audio node to utilize the preloaded audio buffer, since
+ // the browser has already preloaded the audio file.
+ audio = info.audio.cloneNode(true);
+ audio.numChannels = info.audio.numChannels;
+ audio.frequency = info.audio.frequency;
}
+ audio['onended'] = function SDL_audio_onended() { // TODO: cache these
+ if (channelInfo.audio == this) { channelInfo.audio.paused = true; channelInfo.audio = null; }
+ if (SDL.channelFinished) Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel);
+ }
+ channelInfo.audio = audio;
+ // TODO: handle N loops. Behavior matches Mix_PlayMusic
+ audio.loop = loops != 0;
audio.volume = channelInfo.volume;
+ audio.play();
return channel;
},
Mix_PlayChannelTimed: 'Mix_PlayChannel', // XXX ignore Timing
@@ -2383,46 +2494,51 @@ var LibrarySDL = {
Mix_PlayMusic__deps: ['Mix_HaltMusic'],
Mix_PlayMusic: function(id, loops) {
- loops = Math.max(loops, 1);
- var audio = SDL.audios[id].audio;
- if (!audio) return 0;
- audio.loop = loops != 0; // TODO: handle N loops for finite N
- if (SDL.audios[id].buffer) {
- audio["mozWriteAudio"](SDL.audios[id].buffer);
- } else {
- audio.play();
- }
- audio.volume = SDL.music.volume;
- audio['onended'] = _Mix_HaltMusic; // will send callback
+ // Pause old music if it exists.
if (SDL.music.audio) {
- if (!SDL.music.audio.paused) {
- Module.printErr('Music is already playing. ' + SDL.music.source);
- }
+ if (!SDL.music.audio.paused) Module.printErr('Music is already playing. ' + SDL.music.source);
SDL.music.audio.pause();
}
+ var info = SDL.audios[id];
+ var audio;
+ if (info.webAudio) { // Play via Web Audio API
+ // Create an instance of the WebAudio object.
+ audio = {};
+ audio.resource = info; // This new webAudio object is an instance that refers to this existing resource.
+ audio.paused = false;
+ audio.currentPosition = 0;
+ audio.play = function() { SDL.playWebAudio(this); }
+ audio.pause = function() { SDL.pauseWebAudio(this); }
+ } else if (info.audio) { // Play via the <audio> element
+ audio = info.audio;
+ }
+ audio['onended'] = function() { if (SDL.music.audio == this) _Mix_HaltMusic(); } // will send callback
+ audio.loop = loops != 0; // TODO: handle N loops for finite N
+ audio.volume = SDL.music.volume;
SDL.music.audio = audio;
+ audio.play();
return 0;
},
Mix_PauseMusic: function() {
var audio = SDL.music.audio;
- if (!audio) return 0;
- audio.pause();
+ if (audio) audio.pause();
return 0;
},
Mix_ResumeMusic: function() {
var audio = SDL.music.audio;
- if (!audio) return 0;
- audio.play();
+ if (audio) audio.play();
return 0;
},
Mix_HaltMusic: function() {
var audio = SDL.music.audio;
- if (!audio) return 0;
- audio.src = audio.src; // rewind
- audio.pause();
+ if (audio) {
+ audio.src = audio.src; // rewind <media> element
+ audio.currentPosition = 0; // rewind Web Audio graph playback.
+ audio.pause();
+ }
SDL.music.audio = null;
if (SDL.hookMusicFinished) {
Runtime.dynCall('v', SDL.hookMusicFinished);
@@ -2499,9 +2615,7 @@ var LibrarySDL = {
return;
}
var info = SDL.channels[channel];
- if (info && info.audio) {
- info.audio.play();
- }
+ if (info && info.audio) info.audio.play();
},
// SDL TTF
diff --git a/src/preamble.js b/src/preamble.js
index 2aec94c6..f46119c7 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -310,28 +310,6 @@ function assert(condition, text) {
var globalScope = this;
-// C calling interface. A convenient way to call C functions (in C files, or
-// defined with extern "C").
-//
-// Note: LLVM optimizations can inline and remove functions, after which you will not be
-// able to call them. Closure can also do so. To avoid that, add your function to
-// the exports using something like
-//
-// -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]'
-//
-// @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C")
-// @param returnType The return type of the function, one of the JS types 'number', 'string' or 'array' (use 'number' for any C pointer, and
-// 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit).
-// @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType,
-// except that 'array' is not possible (there is no way for us to know the length of the array)
-// @param args An array of the arguments to the function, as native JS values (as in returnType)
-// Note that string arguments will be stored on the stack (the JS string will become a C string on the stack).
-// @return The return value, as a native JS value (as in returnType)
-function ccall(ident, returnType, argTypes, args) {
- return ccallFunc(getCFunc(ident), returnType, argTypes, args);
-}
-Module["ccall"] = ccall;
-
// Returns the C function with a specified identifier (for C++, you need to do manual name mangling)
function getCFunc(ident) {
try {
@@ -343,53 +321,141 @@ function getCFunc(ident) {
return func;
}
-// Internal function that does a C call using a function, not an identifier
-function ccallFunc(func, returnType, argTypes, args) {
+var cwrap, ccall;
+(function(){
var stack = 0;
- function toC(value, type) {
- if (type == 'string') {
- if (value === null || value === undefined || value === 0) return 0; // null string
- value = intArrayFromString(value);
- type = 'array';
- }
- if (type == 'array') {
- if (!stack) stack = Runtime.stackSave();
- var ret = Runtime.stackAlloc(value.length);
- writeArrayToMemory(value, ret);
+ var JSfuncs = {
+ 'stackSave' : function() {
+ stack = Runtime.stackSave();
+ },
+ 'stackRestore' : function() {
+ Runtime.stackRestore(stack);
+ },
+ // type conversion from js to c
+ 'arrayToC' : function(arr) {
+ var ret = Runtime.stackAlloc(arr.length);
+ writeArrayToMemory(arr, ret);
+ return ret;
+ },
+ 'stringToC' : function(str) {
+ var ret = 0;
+ if (str !== null && str !== undefined && str !== 0) { // null string
+ ret = Runtime.stackAlloc(str.length + 1); // +1 for the trailing '\0'
+ writeStringToMemory(str, ret);
+ }
return ret;
}
- return value;
- }
- function fromC(value, type) {
- if (type == 'string') {
- return Pointer_stringify(value);
+ };
+ // For fast lookup of conversion functions
+ var toC = {'string' : JSfuncs['stringToC'], 'array' : JSfuncs['arrayToC']};
+
+ // C calling interface. A convenient way to call C functions (in C files, or
+ // defined with extern "C").
+ //
+ // Note: LLVM optimizations can inline and remove functions, after which you will not be
+ // able to call them. Closure can also do so. To avoid that, add your function to
+ // the exports using something like
+ //
+ // -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]'
+ //
+ // @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C")
+ // @param returnType The return type of the function, one of the JS types 'number', 'string' or 'array' (use 'number' for any C pointer, and
+ // 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit).
+ // @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType,
+ // except that 'array' is not possible (there is no way for us to know the length of the array)
+ // @param args An array of the arguments to the function, as native JS values (as in returnType)
+ // Note that string arguments will be stored on the stack (the JS string will become a C string on the stack).
+ // @return The return value, as a native JS value (as in returnType)
+ ccall = function ccallFunc(ident, returnType, argTypes, args) {