1 /*************************************************************************/
2 /*  audio_driver_javascript.cpp                                          */
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 
31 #include "audio_driver_javascript.h"
32 
33 #include "core/project_settings.h"
34 
35 #include <emscripten.h>
36 
37 AudioDriverJavaScript *AudioDriverJavaScript::singleton = NULL;
38 
is_available()39 bool AudioDriverJavaScript::is_available() {
40 	return EM_ASM_INT({
41 		if (!(window.AudioContext || window.webkitAudioContext)) {
42 			return 0;
43 		}
44 		return 1;
45 	}) != 0;
46 }
47 
get_name() const48 const char *AudioDriverJavaScript::get_name() const {
49 	return "JavaScript";
50 }
51 
audio_driver_js_mix()52 extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() {
53 	AudioDriverJavaScript::singleton->mix_to_js();
54 }
55 
audio_driver_process_capture(float sample)56 extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_capture(float sample) {
57 	AudioDriverJavaScript::singleton->process_capture(sample);
58 }
59 
mix_to_js()60 void AudioDriverJavaScript::mix_to_js() {
61 	int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
62 	int sample_count = memarr_len(internal_buffer) / channel_count;
63 	int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer);
64 	audio_server_process(sample_count, stream_buffer);
65 	for (int i = 0; i < sample_count * channel_count; i++) {
66 		internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.f;
67 	}
68 }
69 
process_capture(float sample)70 void AudioDriverJavaScript::process_capture(float sample) {
71 	int32_t sample32 = int32_t(sample * 32768.f) * (1U << 16);
72 	input_buffer_write(sample32);
73 }
74 
init()75 Error AudioDriverJavaScript::init() {
76 	int mix_rate = GLOBAL_GET("audio/mix_rate");
77 	int latency = GLOBAL_GET("audio/output_latency");
78 
79 	/* clang-format off */
80 	_driver_id = EM_ASM_INT({
81 		const MIX_RATE = $0;
82 		const LATENCY = $1 / 1000;
83 		return Module.IDHandler.add({
84 			'context': new (window.AudioContext || window.webkitAudioContext)({ sampleRate: MIX_RATE, latencyHint: LATENCY}),
85 			'input': null,
86 			'stream': null,
87 			'script': null
88 		});
89 	}, mix_rate, latency);
90 	/* clang-format on */
91 
92 	int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
93 	buffer_length = closest_power_of_2((latency * mix_rate / 1000) * channel_count);
94 	/* clang-format off */
95 	buffer_length = EM_ASM_INT({
96 		var ref = Module.IDHandler.get($0);
97 		const ctx = ref['context'];
98 		const BUFFER_LENGTH = $1;
99 		const CHANNEL_COUNT = $2;
100 
101 		var script = ctx.createScriptProcessor(BUFFER_LENGTH, 2, CHANNEL_COUNT);
102 		script.connect(ctx.destination);
103 		ref['script'] = script;
104 		return script.bufferSize;
105 	}, _driver_id, buffer_length, channel_count);
106 	/* clang-format on */
107 	if (!buffer_length) {
108 		return FAILED;
109 	}
110 
111 	if (!internal_buffer || (int)memarr_len(internal_buffer) != buffer_length * channel_count) {
112 		if (internal_buffer)
113 			memdelete_arr(internal_buffer);
114 		internal_buffer = memnew_arr(float, buffer_length *channel_count);
115 	}
116 
117 	return internal_buffer ? OK : ERR_OUT_OF_MEMORY;
118 }
119 
start()120 void AudioDriverJavaScript::start() {
121 	/* clang-format off */
122 	EM_ASM({
123 		const ref = Module.IDHandler.get($0);
124 		var INTERNAL_BUFFER_PTR = $1;
125 
126 		var audioDriverMixFunction = cwrap('audio_driver_js_mix');
127 		var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']);
128 		ref['script'].onaudioprocess = function(audioProcessingEvent) {
129 			audioDriverMixFunction();
130 
131 			var input = audioProcessingEvent.inputBuffer;
132 			var output = audioProcessingEvent.outputBuffer;
133 			var internalBuffer = HEAPF32.subarray(
134 					INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT,
135 					INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels);
136 
137 			for (var channel = 0; channel < output.numberOfChannels; channel++) {
138 				var outputData = output.getChannelData(channel);
139 				// Loop through samples.
140 				for (var sample = 0; sample < outputData.length; sample++) {
141 					outputData[sample] = internalBuffer[sample * output.numberOfChannels + channel];
142 				}
143 			}
144 
145 			if (ref['input']) {
146 				var inputDataL = input.getChannelData(0);
147 				var inputDataR = input.getChannelData(1);
148 				for (var i = 0; i < inputDataL.length; i++) {
149 					audioDriverProcessCapture(inputDataL[i]);
150 					audioDriverProcessCapture(inputDataR[i]);
151 				}
152 			}
153 		};
154 	}, _driver_id, internal_buffer);
155 	/* clang-format on */
156 }
157 
resume()158 void AudioDriverJavaScript::resume() {
159 	/* clang-format off */
160 	EM_ASM({
161 		const ref = Module.IDHandler.get($0);
162 		if (ref && ref['context'] && ref['context'].resume)
163 			ref['context'].resume();
164 	}, _driver_id);
165 	/* clang-format on */
166 }
167 
get_latency()168 float AudioDriverJavaScript::get_latency() {
169 	/* clang-format off */
170 	return EM_ASM_DOUBLE({
171 		const ref = Module.IDHandler.get($0);
172 		var latency = 0;
173 		if (ref && ref['context']) {
174 			const ctx = ref['context'];
175 			if (ctx.baseLatency) {
176 				latency += ctx.baseLatency;
177 			}
178 			if (ctx.outputLatency) {
179 				latency += ctx.outputLatency;
180 			}
181 		}
182 		return latency;
183 	}, _driver_id);
184 	/* clang-format on */
185 }
186 
get_mix_rate() const187 int AudioDriverJavaScript::get_mix_rate() const {
188 	/* clang-format off */
189 	return EM_ASM_INT({
190 		const ref = Module.IDHandler.get($0);
191 		return ref && ref['context'] ? ref['context'].sampleRate : 0;
192 	}, _driver_id);
193 	/* clang-format on */
194 }
195 
get_speaker_mode() const196 AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const {
197 	/* clang-format off */
198 	return get_speaker_mode_by_total_channels(EM_ASM_INT({
199 		const ref = Module.IDHandler.get($0);
200 		return ref && ref['context'] ? ref['context'].destination.channelCount : 0;
201 	}, _driver_id));
202 	/* clang-format on */
203 }
204 
205 // No locking, as threads are not supported.
lock()206 void AudioDriverJavaScript::lock() {
207 }
208 
unlock()209 void AudioDriverJavaScript::unlock() {
210 }
211 
finish_async()212 void AudioDriverJavaScript::finish_async() {
213 	// Close the context, add the operation to the async_finish list in module.
214 	int id = _driver_id;
215 	_driver_id = 0;
216 
217 	/* clang-format off */
218 	EM_ASM({
219 		const id = $0;
220 		var ref = Module.IDHandler.get(id);
221 		Module.async_finish.push(new Promise(function(accept, reject) {
222 			if (!ref) {
223 				console.log("Ref not found!", id, Module.IDHandler);
224 				setTimeout(accept, 0);
225 			} else {
226 				Module.IDHandler.remove(id);
227 				const context = ref['context'];
228 				// Disconnect script and input.
229 				ref['script'].disconnect();
230 				if (ref['input'])
231 					ref['input'].disconnect();
232 				ref = null;
233 				context.close().then(function() {
234 					accept();
235 				}).catch(function(e) {
236 					accept();
237 				});
238 			}
239 		}));
240 	}, id);
241 	/* clang-format on */
242 }
243 
finish()244 void AudioDriverJavaScript::finish() {
245 	if (internal_buffer) {
246 		memdelete_arr(internal_buffer);
247 		internal_buffer = NULL;
248 	}
249 }
250 
capture_start()251 Error AudioDriverJavaScript::capture_start() {
252 	input_buffer_init(buffer_length);
253 
254 	/* clang-format off */
255 	EM_ASM({
256 		function gotMediaInput(stream) {
257 			var ref = Module.IDHandler.get($0);
258 			ref['stream'] = stream;
259 			ref['input'] = ref['context'].createMediaStreamSource(stream);
260 			ref['input'].connect(ref['script']);
261 		}
262 
263 		function gotMediaInputError(e) {
264 			out(e);
265 		}
266 
267 		if (navigator.mediaDevices.getUserMedia) {
268 			navigator.mediaDevices.getUserMedia({"audio": true}).then(gotMediaInput, gotMediaInputError);
269 		} else {
270 			if (!navigator.getUserMedia)
271 				navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
272 			navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError);
273 		}
274 	}, _driver_id);
275 	/* clang-format on */
276 
277 	return OK;
278 }
279 
capture_stop()280 Error AudioDriverJavaScript::capture_stop() {
281 	/* clang-format off */
282 	EM_ASM({
283 		var ref = Module.IDHandler.get($0);
284 		if (ref['stream']) {
285 			const tracks = ref['stream'].getTracks();
286 			for (var i = 0; i < tracks.length; i++) {
287 				tracks[i].stop();
288 			}
289 			ref['stream'] = null;
290 		}
291 
292 		if (ref['input']) {
293 			ref['input'].disconnect();
294 			ref['input'] = null;
295 		}
296 
297 	}, _driver_id);
298 	/* clang-format on */
299 
300 	input_buffer.clear();
301 
302 	return OK;
303 }
304 
AudioDriverJavaScript()305 AudioDriverJavaScript::AudioDriverJavaScript() {
306 	_driver_id = 0;
307 	internal_buffer = NULL;
308 	buffer_length = 0;
309 
310 	singleton = this;
311 }
312