diff options
Diffstat (limited to 'platform/javascript/native')
-rw-r--r-- | platform/javascript/native/library_godot_audio.js | 173 | ||||
-rw-r--r-- | platform/javascript/native/utils.js | 90 |
2 files changed, 262 insertions, 1 deletions
diff --git a/platform/javascript/native/library_godot_audio.js b/platform/javascript/native/library_godot_audio.js new file mode 100644 index 0000000000..d300280ccd --- /dev/null +++ b/platform/javascript/native/library_godot_audio.js @@ -0,0 +1,173 @@ +/*************************************************************************/ +/* library_godot_audio.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +var GodotAudio = { + + $GodotAudio: { + + ctx: null, + input: null, + script: null, + }, + + godot_audio_is_available__proxy: 'sync', + godot_audio_is_available: function () { + if (!(window.AudioContext || window.webkitAudioContext)) { + return 0; + } + return 1; + }, + + godot_audio_init: function(mix_rate, latency) { + GodotAudio.ctx = new (window.AudioContext || window.webkitAudioContext)({ + sampleRate: mix_rate, + latencyHint: latency + }); + return GodotAudio.ctx.destination.channelCount; + }, + + godot_audio_create_processor: function(buffer_length, channel_count) { + GodotAudio.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count); + GodotAudio.script.connect(GodotAudio.ctx.destination); + return GodotAudio.script.bufferSize; + }, + + godot_audio_start: function(buffer_ptr) { + var audioDriverProcessStart = cwrap('audio_driver_process_start'); + var audioDriverProcessEnd = cwrap('audio_driver_process_end'); + var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']); + GodotAudio.script.onaudioprocess = function(audioProcessingEvent) { + audioDriverProcessStart(); + + var input = audioProcessingEvent.inputBuffer; + var output = audioProcessingEvent.outputBuffer; + var internalBuffer = HEAPF32.subarray( + buffer_ptr / HEAPF32.BYTES_PER_ELEMENT, + buffer_ptr / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels); + for (var channel = 0; channel < output.numberOfChannels; channel++) { + var outputData = output.getChannelData(channel); + // Loop through samples. + for (var sample = 0; sample < outputData.length; sample++) { + outputData[sample] = internalBuffer[sample * output.numberOfChannels + channel]; + } + } + + if (GodotAudio.input) { + var inputDataL = input.getChannelData(0); + var inputDataR = input.getChannelData(1); + for (var i = 0; i < inputDataL.length; i++) { + audioDriverProcessCapture(inputDataL[i]); + audioDriverProcessCapture(inputDataR[i]); + } + } + audioDriverProcessEnd(); + }; + }, + + godot_audio_resume: function() { + if (GodotAudio.ctx && GodotAudio.ctx.state != 'running') { + GodotAudio.ctx.resume(); + } + }, + + godot_audio_finish_async: function() { + Module.async_finish.push(new Promise(function(accept, reject) { + if (!GodotAudio.ctx) { + setTimeout(accept, 0); + } else { + if (GodotAudio.script) { + GodotAudio.script.disconnect(); + GodotAudio.script = null; + } + if (GodotAudio.input) { + GodotAudio.input.disconnect(); + GodotAudio.input = null; + } + GodotAudio.ctx.close().then(function() { + accept(); + }).catch(function(e) { + accept(); + }); + GodotAudio.ctx = null; + } + })); + }, + + godot_audio_get_latency__proxy: 'sync', + godot_audio_get_latency: function() { + var latency = 0; + if (GodotAudio.ctx) { + if (GodotAudio.ctx.baseLatency) { + latency += GodotAudio.ctx.baseLatency; + } + if (GodotAudio.ctx.outputLatency) { + latency += GodotAudio.ctx.outputLatency; + } + } + return latency; + }, + + godot_audio_capture_start__proxy: 'sync', + godot_audio_capture_start: function() { + if (GodotAudio.input) { + return; // Already started. + } + function gotMediaInput(stream) { + GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); + GodotAudio.input.connect(GodotAudio.script); + } + + function gotMediaInputError(e) { + out(e); + } + + if (navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia({"audio": true}).then(gotMediaInput, gotMediaInputError); + } else { + if (!navigator.getUserMedia) + navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError); + } + }, + + godot_audio_capture_stop__proxy: 'sync', + godot_audio_capture_stop: function() { + if (GodotAudio.input) { + const tracks = GodotAudio.input.mediaStream.getTracks(); + for (var i = 0; i < tracks.length; i++) { + tracks[i].stop(); + } + GodotAudio.input.disconnect(); + GodotAudio.input = null; + } + }, +}; + +autoAddDeps(GodotAudio, "$GodotAudio"); +mergeInto(LibraryManager.library, GodotAudio); diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js index 95585d26ae..8d0beba454 100644 --- a/platform/javascript/native/utils.js +++ b/platform/javascript/native/utils.js @@ -28,6 +28,53 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +Module['initFS'] = function(persistentPaths) { + Module.mount_points = ['/userfs'].concat(persistentPaths); + + function createRecursive(dir) { + try { + FS.stat(dir); + } catch (e) { + if (e.errno !== ERRNO_CODES.ENOENT) { + throw e; + } + FS.mkdirTree(dir); + } + } + + Module.mount_points.forEach(function(path) { + createRecursive(path); + FS.mount(IDBFS, {}, path); + }); + return new Promise(function(resolve, reject) { + FS.syncfs(true, function(err) { + if (err) { + Module.mount_points = []; + Module.idbfs = false; + console.log("IndexedDB not available: " + err.message); + } else { + Module.idbfs = true; + } + resolve(err); + }); + }); +}; + +Module['deinitFS'] = function() { + Module.mount_points.forEach(function(path) { + try { + FS.unmount(path); + } catch (e) { + console.log("Already unmounted", e); + } + if (Module.idbfs && IDBFS.dbs[path]) { + IDBFS.dbs[path].close(); + delete IDBFS.dbs[path]; + } + }); + Module.mount_points = []; +}; + Module['copyToFS'] = function(path, buffer) { var p = path.lastIndexOf("/"); var dir = "/"; @@ -37,7 +84,7 @@ Module['copyToFS'] = function(path, buffer) { try { FS.stat(dir); } catch (e) { - if (e.errno !== ERRNO_CODES.ENOENT) { // 'ENOENT', see https://github.com/emscripten-core/emscripten/blob/master/system/lib/libc/musl/arch/emscripten/bits/errno.h + if (e.errno !== ERRNO_CODES.ENOENT) { throw e; } FS.mkdirTree(dir); @@ -202,3 +249,44 @@ Module.drop_handler = (function() { }); } })(); + +function EventHandlers() { + function Handler(target, event, method, capture) { + this.target = target; + this.event = event; + this.method = method; + this.capture = capture; + } + + var listeners = []; + + function has(target, event, method, capture) { + return listeners.findIndex(function(e) { + return e.target === target && e.event === event && e.method === method && e.capture == capture; + }) !== -1; + } + + this.add = function(target, event, method, capture) { + if (has(target, event, method, capture)) { + return; + } + listeners.push(new Handler(target, event, method, capture)); + target.addEventListener(event, method, capture); + }; + + this.remove = function(target, event, method, capture) { + if (!has(target, event, method, capture)) { + return; + } + target.removeEventListener(event, method, capture); + }; + + this.clear = function() { + listeners.forEach(function(h) { + h.target.removeEventListener(h.event, h.method, h.capture); + }); + listeners.length = 0; + }; +} + +Module.listeners = new EventHandlers(); |