CoolFace
Apppublic

Jofthomas/Everchanging-Quest

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
187likes
index.audio.worklet.js214 linesDownload Raw Back to root
1/**************************************************************************/2/*  audio.worklet.js                                                      */3/**************************************************************************/4/*                         This file is part of:                          */5/*                             GODOT ENGINE                               */6/*                        https://godotengine.org                         */7/**************************************************************************/8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */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 31class RingBuffer {32	constructor(p_buffer, p_state, p_threads) {33		this.buffer = p_buffer;34		this.avail = p_state;35		this.threads = p_threads;36		this.rpos = 0;37		this.wpos = 0;38	}39 40	data_left() {41		return this.threads ? Atomics.load(this.avail, 0) : this.avail;42	}43 44	space_left() {45		return this.buffer.length - this.data_left();46	}47 48	read(output) {49		const size = this.buffer.length;50		let from = 0;51		let to_write = output.length;52		if (this.rpos + to_write > size) {53			const high = size - this.rpos;54			output.set(this.buffer.subarray(this.rpos, size));55			from = high;56			to_write -= high;57			this.rpos = 0;58		}59		if (to_write) {60			output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);61		}62		this.rpos += to_write;63		if (this.threads) {64			Atomics.add(this.avail, 0, -output.length);65			Atomics.notify(this.avail, 0);66		} else {67			this.avail -= output.length;68		}69	}70 71	write(p_buffer) {72		const to_write = p_buffer.length;73		const mw = this.buffer.length - this.wpos;74		if (mw >= to_write) {75			this.buffer.set(p_buffer, this.wpos);76			this.wpos += to_write;77			if (mw === to_write) {78				this.wpos = 0;79			}80		} else {81			const high = p_buffer.subarray(0, mw);82			const low = p_buffer.subarray(mw);83			this.buffer.set(high, this.wpos);84			this.buffer.set(low);85			this.wpos = low.length;86		}87		if (this.threads) {88			Atomics.add(this.avail, 0, to_write);89			Atomics.notify(this.avail, 0);90		} else {91			this.avail += to_write;92		}93	}94}95 96class GodotProcessor extends AudioWorkletProcessor {97	constructor() {98		super();99		this.threads = false;100		this.running = true;101		this.lock = null;102		this.notifier = null;103		this.output = null;104		this.output_buffer = new Float32Array();105		this.input = null;106		this.input_buffer = new Float32Array();107		this.port.onmessage = (event) => {108			const cmd = event.data['cmd'];109			const data = event.data['data'];110			this.parse_message(cmd, data);111		};112	}113 114	process_notify() {115		if (this.notifier) {116			Atomics.add(this.notifier, 0, 1);117			Atomics.notify(this.notifier, 0);118		}119	}120 121	parse_message(p_cmd, p_data) {122		if (p_cmd === 'start' && p_data) {123			const state = p_data[0];124			let idx = 0;125			this.threads = true;126			this.lock = state.subarray(idx, ++idx);127			this.notifier = state.subarray(idx, ++idx);128			const avail_in = state.subarray(idx, ++idx);129			const avail_out = state.subarray(idx, ++idx);130			this.input = new RingBuffer(p_data[1], avail_in, true);131			this.output = new RingBuffer(p_data[2], avail_out, true);132		} else if (p_cmd === 'stop') {133			this.running = false;134			this.output = null;135			this.input = null;136			this.lock = null;137			this.notifier = null;138		} else if (p_cmd === 'start_nothreads') {139			this.output = new RingBuffer(p_data[0], p_data[0].length, false);140		} else if (p_cmd === 'chunk') {141			this.output.write(p_data);142		}143	}144 145	static array_has_data(arr) {146		return arr.length && arr[0].length && arr[0][0].length;147	}148 149	process(inputs, outputs, parameters) {150		if (!this.running) {151			return false; // Stop processing.152		}153		if (this.output === null) {154			return true; // Not ready yet, keep processing.155		}156		const process_input = GodotProcessor.array_has_data(inputs);157		if (process_input) {158			const input = inputs[0];159			const chunk = input[0].length * input.length;160			if (this.input_buffer.length !== chunk) {161				this.input_buffer = new Float32Array(chunk);162			}163			if (!this.threads) {164				GodotProcessor.write_input(this.input_buffer, input);165				this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer });166			} else if (this.input.space_left() >= chunk) {167				GodotProcessor.write_input(this.input_buffer, input);168				this.input.write(this.input_buffer);169			} else {170				// this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.171			}172		}173		const process_output = GodotProcessor.array_has_data(outputs);174		if (process_output) {175			const output = outputs[0];176			const chunk = output[0].length * output.length;177			if (this.output_buffer.length !== chunk) {178				this.output_buffer = new Float32Array(chunk);179			}180			if (this.output.data_left() >= chunk) {181				this.output.read(this.output_buffer);182				GodotProcessor.write_output(output, this.output_buffer);183				if (!this.threads) {184					this.port.postMessage({ 'cmd': 'read', 'data': chunk });185				}186			} else {187				// this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.188			}189		}190		this.process_notify();191		return true;192	}193 194	static write_output(dest, source) {195		const channels = dest.length;196		for (let ch = 0; ch < channels; ch++) {197			for (let sample = 0; sample < dest[ch].length; sample++) {198				dest[ch][sample] = source[sample * channels + ch];199			}200		}201	}202 203	static write_input(dest, source) {204		const channels = source.length;205		for (let ch = 0; ch < channels; ch++) {206			for (let sample = 0; sample < source[ch].length; sample++) {207				dest[sample * channels + ch] = source[ch][sample];208			}209		}210	}211}212 213registerProcessor('godot-processor', GodotProcessor);214