library_godot_audio.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /*************************************************************************/
  2. /* library_godot_audio.js */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. const GodotAudio = {
  31. $GodotAudio__deps: ['$GodotRuntime', '$GodotOS'],
  32. $GodotAudio: {
  33. ctx: null,
  34. input: null,
  35. driver: null,
  36. interval: 0,
  37. init: function (mix_rate, latency, onstatechange, onlatencyupdate) {
  38. const ctx = new (window.AudioContext || window.webkitAudioContext)({
  39. sampleRate: mix_rate,
  40. // latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance.
  41. });
  42. GodotAudio.ctx = ctx;
  43. ctx.onstatechange = function () {
  44. let state = 0;
  45. switch (ctx.state) {
  46. case 'suspended':
  47. state = 0;
  48. break;
  49. case 'running':
  50. state = 1;
  51. break;
  52. case 'closed':
  53. state = 2;
  54. break;
  55. // no default
  56. }
  57. onstatechange(state);
  58. };
  59. ctx.onstatechange(); // Immeditately notify state.
  60. // Update computed latency
  61. GodotAudio.interval = setInterval(function () {
  62. let computed_latency = 0;
  63. if (ctx.baseLatency) {
  64. computed_latency += GodotAudio.ctx.baseLatency;
  65. }
  66. if (ctx.outputLatency) {
  67. computed_latency += GodotAudio.ctx.outputLatency;
  68. }
  69. onlatencyupdate(computed_latency);
  70. }, 1000);
  71. GodotOS.atexit(GodotAudio.close_async);
  72. return ctx.destination.channelCount;
  73. },
  74. create_input: function (callback) {
  75. if (GodotAudio.input) {
  76. return; // Already started.
  77. }
  78. function gotMediaInput(stream) {
  79. GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
  80. callback(GodotAudio.input);
  81. }
  82. if (navigator.mediaDevices.getUserMedia) {
  83. navigator.mediaDevices.getUserMedia({
  84. 'audio': true,
  85. }).then(gotMediaInput, function (e) {
  86. GodotRuntime.print(e);
  87. });
  88. } else {
  89. if (!navigator.getUserMedia) {
  90. navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
  91. }
  92. navigator.getUserMedia({
  93. 'audio': true,
  94. }, gotMediaInput, function (e) {
  95. GodotRuntime.print(e);
  96. });
  97. }
  98. },
  99. close_async: function (resolve, reject) {
  100. const ctx = GodotAudio.ctx;
  101. GodotAudio.ctx = null;
  102. // Audio was not initialized.
  103. if (!ctx) {
  104. resolve();
  105. return;
  106. }
  107. // Remove latency callback
  108. if (GodotAudio.interval) {
  109. clearInterval(GodotAudio.interval);
  110. GodotAudio.interval = 0;
  111. }
  112. // Disconnect input, if it was started.
  113. if (GodotAudio.input) {
  114. GodotAudio.input.disconnect();
  115. GodotAudio.input = null;
  116. }
  117. // Disconnect output
  118. let closed = Promise.resolve();
  119. if (GodotAudio.driver) {
  120. closed = GodotAudio.driver.close();
  121. }
  122. closed.then(function () {
  123. return ctx.close();
  124. }).then(function () {
  125. ctx.onstatechange = null;
  126. resolve();
  127. }).catch(function (e) {
  128. ctx.onstatechange = null;
  129. GodotRuntime.error('Error closing AudioContext', e);
  130. resolve();
  131. });
  132. },
  133. },
  134. godot_audio_is_available__proxy: 'sync',
  135. godot_audio_is_available: function () {
  136. if (!(window.AudioContext || window.webkitAudioContext)) {
  137. return 0;
  138. }
  139. return 1;
  140. },
  141. godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
  142. const statechange = GodotRuntime.get_func(p_state_change);
  143. const latencyupdate = GodotRuntime.get_func(p_latency_update);
  144. return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate);
  145. },
  146. godot_audio_resume: function () {
  147. if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
  148. GodotAudio.ctx.resume();
  149. }
  150. },
  151. godot_audio_capture_start__proxy: 'sync',
  152. godot_audio_capture_start: function () {
  153. if (GodotAudio.input) {
  154. return; // Already started.
  155. }
  156. GodotAudio.create_input(function (input) {
  157. input.connect(GodotAudio.driver.get_node());
  158. });
  159. },
  160. godot_audio_capture_stop__proxy: 'sync',
  161. godot_audio_capture_stop: function () {
  162. if (GodotAudio.input) {
  163. const tracks = GodotAudio.input['mediaStream']['getTracks']();
  164. for (let i = 0; i < tracks.length; i++) {
  165. tracks[i]['stop']();
  166. }
  167. GodotAudio.input.disconnect();
  168. GodotAudio.input = null;
  169. }
  170. },
  171. };
  172. autoAddDeps(GodotAudio, '$GodotAudio');
  173. mergeInto(LibraryManager.library, GodotAudio);
  174. /**
  175. * The AudioWorklet API driver, used when threads are available.
  176. */
  177. const GodotAudioWorklet = {
  178. $GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'],
  179. $GodotAudioWorklet: {
  180. promise: null,
  181. worklet: null,
  182. create: function (channels) {
  183. const path = GodotConfig.locate_file('godot.audio.worklet.js');
  184. GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function () {
  185. GodotAudioWorklet.worklet = new AudioWorkletNode(
  186. GodotAudio.ctx,
  187. 'godot-processor',
  188. {
  189. 'outputChannelCount': [channels],
  190. },
  191. );
  192. return Promise.resolve();
  193. });
  194. GodotAudio.driver = GodotAudioWorklet;
  195. },
  196. start: function (in_buf, out_buf, state) {
  197. GodotAudioWorklet.promise.then(function () {
  198. const node = GodotAudioWorklet.worklet;
  199. node.connect(GodotAudio.ctx.destination);
  200. node.port.postMessage({
  201. 'cmd': 'start',
  202. 'data': [state, in_buf, out_buf],
  203. });
  204. node.port.onmessage = function (event) {
  205. GodotRuntime.error(event.data);
  206. };
  207. });
  208. },
  209. get_node: function () {
  210. return GodotAudioWorklet.worklet;
  211. },
  212. close: function () {
  213. return new Promise(function (resolve, reject) {
  214. GodotAudioWorklet.promise.then(function () {
  215. GodotAudioWorklet.worklet.port.postMessage({
  216. 'cmd': 'stop',
  217. 'data': null,
  218. });
  219. GodotAudioWorklet.worklet.disconnect();
  220. GodotAudioWorklet.worklet = null;
  221. GodotAudioWorklet.promise = null;
  222. resolve();
  223. });
  224. });
  225. },
  226. },
  227. godot_audio_worklet_create: function (channels) {
  228. GodotAudioWorklet.create(channels);
  229. },
  230. godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
  231. const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
  232. const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
  233. const state = GodotRuntime.heapSub(HEAP32, p_state, 4);
  234. GodotAudioWorklet.start(in_buffer, out_buffer, state);
  235. },
  236. godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) {
  237. Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
  238. return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
  239. },
  240. godot_audio_worklet_state_add: function (p_state, p_idx, p_value) {
  241. return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
  242. },
  243. godot_audio_worklet_state_get: function (p_state, p_idx) {
  244. return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
  245. },
  246. };
  247. autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
  248. mergeInto(LibraryManager.library, GodotAudioWorklet);
  249. /*
  250. * The deprecated ScriptProcessorNode API, used when threads are disabled.
  251. */
  252. const GodotAudioScript = {
  253. $GodotAudioScript__deps: ['$GodotAudio'],
  254. $GodotAudioScript: {
  255. script: null,
  256. create: function (buffer_length, channel_count) {
  257. GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count);
  258. GodotAudio.driver = GodotAudioScript;
  259. return GodotAudioScript.script.bufferSize;
  260. },
  261. start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
  262. GodotAudioScript.script.onaudioprocess = function (event) {
  263. // Read input
  264. const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
  265. const input = event.inputBuffer;
  266. if (GodotAudio.input) {
  267. const inlen = input.getChannelData(0).length;
  268. for (let ch = 0; ch < 2; ch++) {
  269. const data = input.getChannelData(ch);
  270. for (let s = 0; s < inlen; s++) {
  271. inb[s * 2 + ch] = data[s];
  272. }
  273. }
  274. }
  275. // Let Godot process the input/output.
  276. onprocess();
  277. // Write the output.
  278. const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
  279. const output = event.outputBuffer;
  280. const channels = output.numberOfChannels;
  281. for (let ch = 0; ch < channels; ch++) {
  282. const data = output.getChannelData(ch);
  283. // Loop through samples and assign computed values.
  284. for (let sample = 0; sample < data.length; sample++) {
  285. data[sample] = outb[sample * channels + ch];
  286. }
  287. }
  288. };
  289. GodotAudioScript.script.connect(GodotAudio.ctx.destination);
  290. },
  291. get_node: function () {
  292. return GodotAudioScript.script;
  293. },
  294. close: function () {
  295. return new Promise(function (resolve, reject) {
  296. GodotAudioScript.script.disconnect();
  297. GodotAudioScript.script.onaudioprocess = null;
  298. GodotAudioScript.script = null;
  299. resolve();
  300. });
  301. },
  302. },
  303. godot_audio_script_create: function (buffer_length, channel_count) {
  304. return GodotAudioScript.create(buffer_length, channel_count);
  305. },
  306. godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
  307. const onprocess = GodotRuntime.get_func(p_cb);
  308. GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);
  309. },
  310. };
  311. autoAddDeps(GodotAudioScript, '$GodotAudioScript');
  312. mergeInto(LibraryManager.library, GodotAudioScript);