diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/embind/emval.js | 5 | ||||
-rw-r--r-- | src/library_browser.js | 42 | ||||
-rw-r--r-- | src/library_html5.js | 6 | ||||
-rw-r--r-- | src/library_sdl.js | 372 | ||||
-rw-r--r-- | src/preamble.js | 188 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 9 | ||||
-rw-r--r-- | src/relooper/fuzzer.py | 6 | ||||
-rw-r--r-- | src/relooper/test.cpp | 31 | ||||
-rw-r--r-- | src/relooper/test.txt | 153 | ||||
-rw-r--r-- | src/runtime.js | 9 | ||||
-rw-r--r-- | src/shell.html | 11 | ||||
-rw-r--r-- | src/shell_minimal.html | 11 | ||||
-rw-r--r-- | src/struct_info.json | 18 |
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) { |