| 
									
										
										
										
											2023-01-10 15:26:54 +01:00
										 |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /*  audio.worklet.js                                                      */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /*                         This file is part of:                          */ | 
					
						
							|  |  |  | /*                             GODOT ENGINE                               */ | 
					
						
							|  |  |  | /*                        https://godotengine.org                         */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							|  |  |  | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | 
					
						
							|  |  |  | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | 
					
						
							|  |  |  | /*                                                                        */ | 
					
						
							|  |  |  | /* 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.                 */ | 
					
						
							|  |  |  | /**************************************************************************/ | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | class RingBuffer { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 	constructor(p_buffer, p_state, p_threads) { | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		this.buffer = p_buffer; | 
					
						
							|  |  |  | 		this.avail = p_state; | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		this.threads = p_threads; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		this.rpos = 0; | 
					
						
							|  |  |  | 		this.wpos = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data_left() { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		return this.threads ? Atomics.load(this.avail, 0) : this.avail; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	space_left() { | 
					
						
							|  |  |  | 		return this.buffer.length - this.data_left(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	read(output) { | 
					
						
							|  |  |  | 		const size = this.buffer.length; | 
					
						
							| 
									
										
										
										
											2020-11-17 18:18:46 +00:00
										 |  |  | 		let from = 0; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		let to_write = output.length; | 
					
						
							|  |  |  | 		if (this.rpos + to_write > size) { | 
					
						
							|  |  |  | 			const high = size - this.rpos; | 
					
						
							|  |  |  | 			output.set(this.buffer.subarray(this.rpos, size)); | 
					
						
							|  |  |  | 			from = high; | 
					
						
							|  |  |  | 			to_write -= high; | 
					
						
							|  |  |  | 			this.rpos = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		if (to_write) { | 
					
						
							|  |  |  | 			output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		this.rpos += to_write; | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		if (this.threads) { | 
					
						
							|  |  |  | 			Atomics.add(this.avail, 0, -output.length); | 
					
						
							|  |  |  | 			Atomics.notify(this.avail, 0); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			this.avail -= output.length; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	write(p_buffer) { | 
					
						
							|  |  |  | 		const to_write = p_buffer.length; | 
					
						
							|  |  |  | 		const mw = this.buffer.length - this.wpos; | 
					
						
							|  |  |  | 		if (mw >= to_write) { | 
					
						
							|  |  |  | 			this.buffer.set(p_buffer, this.wpos); | 
					
						
							| 
									
										
										
										
											2021-09-14 03:38:52 +02:00
										 |  |  | 			this.wpos += to_write; | 
					
						
							|  |  |  | 			if (mw === to_write) { | 
					
						
							|  |  |  | 				this.wpos = 0; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2021-09-14 03:38:52 +02:00
										 |  |  | 			const high = p_buffer.subarray(0, mw); | 
					
						
							|  |  |  | 			const low = p_buffer.subarray(mw); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			this.buffer.set(high, this.wpos); | 
					
						
							|  |  |  | 			this.buffer.set(low); | 
					
						
							| 
									
										
										
										
											2021-09-14 03:38:52 +02:00
										 |  |  | 			this.wpos = low.length; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		if (this.threads) { | 
					
						
							|  |  |  | 			Atomics.add(this.avail, 0, to_write); | 
					
						
							|  |  |  | 			Atomics.notify(this.avail, 0); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			this.avail += to_write; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class GodotProcessor extends AudioWorkletProcessor { | 
					
						
							|  |  |  | 	constructor() { | 
					
						
							|  |  |  | 		super(); | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		this.threads = false; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		this.running = true; | 
					
						
							|  |  |  | 		this.lock = null; | 
					
						
							|  |  |  | 		this.notifier = null; | 
					
						
							|  |  |  | 		this.output = null; | 
					
						
							|  |  |  | 		this.output_buffer = new Float32Array(); | 
					
						
							|  |  |  | 		this.input = null; | 
					
						
							|  |  |  | 		this.input_buffer = new Float32Array(); | 
					
						
							|  |  |  | 		this.port.onmessage = (event) => { | 
					
						
							|  |  |  | 			const cmd = event.data['cmd']; | 
					
						
							|  |  |  | 			const data = event.data['data']; | 
					
						
							|  |  |  | 			this.parse_message(cmd, data); | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	process_notify() { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		if (this.notifier) { | 
					
						
							|  |  |  | 			Atomics.add(this.notifier, 0, 1); | 
					
						
							|  |  |  | 			Atomics.notify(this.notifier, 0); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	parse_message(p_cmd, p_data) { | 
					
						
							| 
									
										
										
										
											2020-11-23 13:27:13 +01:00
										 |  |  | 		if (p_cmd === 'start' && p_data) { | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			const state = p_data[0]; | 
					
						
							|  |  |  | 			let idx = 0; | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 			this.threads = true; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			this.lock = state.subarray(idx, ++idx); | 
					
						
							|  |  |  | 			this.notifier = state.subarray(idx, ++idx); | 
					
						
							|  |  |  | 			const avail_in = state.subarray(idx, ++idx); | 
					
						
							|  |  |  | 			const avail_out = state.subarray(idx, ++idx); | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 			this.input = new RingBuffer(p_data[1], avail_in, true); | 
					
						
							|  |  |  | 			this.output = new RingBuffer(p_data[2], avail_out, true); | 
					
						
							| 
									
										
										
										
											2020-11-23 13:27:13 +01:00
										 |  |  | 		} else if (p_cmd === 'stop') { | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 			this.running = false; | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			this.output = null; | 
					
						
							|  |  |  | 			this.input = null; | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 		} else if (p_cmd === 'start_nothreads') { | 
					
						
							|  |  |  | 			this.output = new RingBuffer(p_data[0], p_data[0].length, false); | 
					
						
							|  |  |  | 		} else if (p_cmd === 'chunk') { | 
					
						
							|  |  |  | 			this.output.write(p_data); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 	static array_has_data(arr) { | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		return arr.length && arr[0].length && arr[0][0].length; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	process(inputs, outputs, parameters) { | 
					
						
							|  |  |  | 		if (!this.running) { | 
					
						
							|  |  |  | 			return false; // Stop processing.
 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (this.output === null) { | 
					
						
							|  |  |  | 			return true; // Not ready yet, keep processing.
 | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 		const process_input = GodotProcessor.array_has_data(inputs); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		if (process_input) { | 
					
						
							|  |  |  | 			const input = inputs[0]; | 
					
						
							|  |  |  | 			const chunk = input[0].length * input.length; | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 			if (this.input_buffer.length !== chunk) { | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 				this.input_buffer = new Float32Array(chunk); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 			if (!this.threads) { | 
					
						
							|  |  |  | 				GodotProcessor.write_input(this.input_buffer, input); | 
					
						
							|  |  |  | 				this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer }); | 
					
						
							|  |  |  | 			} else if (this.input.space_left() >= chunk) { | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 				GodotProcessor.write_input(this.input_buffer, input); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 				this.input.write(this.input_buffer); | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2020-11-23 13:27:13 +01:00
										 |  |  | 				this.port.postMessage('Input buffer is full! Skipping input frame.'); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 		const process_output = GodotProcessor.array_has_data(outputs); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		if (process_output) { | 
					
						
							|  |  |  | 			const output = outputs[0]; | 
					
						
							|  |  |  | 			const chunk = output[0].length * output.length; | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 			if (this.output_buffer.length !== chunk) { | 
					
						
							| 
									
										
										
										
											2020-11-17 18:18:46 +00:00
										 |  |  | 				this.output_buffer = new Float32Array(chunk); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			if (this.output.data_left() >= chunk) { | 
					
						
							|  |  |  | 				this.output.read(this.output_buffer); | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 				GodotProcessor.write_output(output, this.output_buffer); | 
					
						
							| 
									
										
										
										
											2021-09-12 19:23:30 +02:00
										 |  |  | 				if (!this.threads) { | 
					
						
							|  |  |  | 					this.port.postMessage({ 'cmd': 'read', 'data': chunk }); | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2020-11-23 13:27:13 +01:00
										 |  |  | 				this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this.process_notify(); | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 	static write_output(dest, source) { | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		const channels = dest.length; | 
					
						
							|  |  |  | 		for (let ch = 0; ch < channels; ch++) { | 
					
						
							|  |  |  | 			for (let sample = 0; sample < dest[ch].length; sample++) { | 
					
						
							|  |  |  | 				dest[ch][sample] = source[sample * channels + ch]; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 16:54:07 +01:00
										 |  |  | 	static write_input(dest, source) { | 
					
						
							| 
									
										
										
										
											2020-11-03 17:18:02 +01:00
										 |  |  | 		const channels = source.length; | 
					
						
							|  |  |  | 		for (let ch = 0; ch < channels; ch++) { | 
					
						
							|  |  |  | 			for (let sample = 0; sample < source[ch].length; sample++) { | 
					
						
							|  |  |  | 				dest[sample * channels + ch] = source[ch][sample]; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | registerProcessor('godot-processor', GodotProcessor); |