Path: blob/master/platform/web/js/libs/audio.worklet.js
10279 views
/**************************************************************************/1/* audio.worklet.js */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930class RingBuffer {31constructor(p_buffer, p_state, p_threads) {32this.buffer = p_buffer;33this.avail = p_state;34this.threads = p_threads;35this.rpos = 0;36this.wpos = 0;37}3839data_left() {40return this.threads ? Atomics.load(this.avail, 0) : this.avail;41}4243space_left() {44return this.buffer.length - this.data_left();45}4647read(output) {48const size = this.buffer.length;49let from = 0;50let to_write = output.length;51if (this.rpos + to_write > size) {52const high = size - this.rpos;53output.set(this.buffer.subarray(this.rpos, size));54from = high;55to_write -= high;56this.rpos = 0;57}58if (to_write) {59output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);60}61this.rpos += to_write;62if (this.threads) {63Atomics.add(this.avail, 0, -output.length);64Atomics.notify(this.avail, 0);65} else {66this.avail -= output.length;67}68}6970write(p_buffer) {71const to_write = p_buffer.length;72const mw = this.buffer.length - this.wpos;73if (mw >= to_write) {74this.buffer.set(p_buffer, this.wpos);75this.wpos += to_write;76if (mw === to_write) {77this.wpos = 0;78}79} else {80const high = p_buffer.subarray(0, mw);81const low = p_buffer.subarray(mw);82this.buffer.set(high, this.wpos);83this.buffer.set(low);84this.wpos = low.length;85}86if (this.threads) {87Atomics.add(this.avail, 0, to_write);88Atomics.notify(this.avail, 0);89} else {90this.avail += to_write;91}92}93}9495class GodotProcessor extends AudioWorkletProcessor {96constructor() {97super();98this.threads = false;99this.running = true;100this.lock = null;101this.notifier = null;102this.output = null;103this.output_buffer = new Float32Array();104this.input = null;105this.input_buffer = new Float32Array();106this.port.onmessage = (event) => {107const cmd = event.data['cmd'];108const data = event.data['data'];109this.parse_message(cmd, data);110};111}112113process_notify() {114if (this.notifier) {115Atomics.add(this.notifier, 0, 1);116Atomics.notify(this.notifier, 0);117}118}119120parse_message(p_cmd, p_data) {121if (p_cmd === 'start' && p_data) {122const state = p_data[0];123let idx = 0;124this.threads = true;125this.lock = state.subarray(idx, ++idx);126this.notifier = state.subarray(idx, ++idx);127const avail_in = state.subarray(idx, ++idx);128const avail_out = state.subarray(idx, ++idx);129this.input = new RingBuffer(p_data[1], avail_in, true);130this.output = new RingBuffer(p_data[2], avail_out, true);131} else if (p_cmd === 'stop') {132this.running = false;133this.output = null;134this.input = null;135this.lock = null;136this.notifier = null;137} else if (p_cmd === 'start_nothreads') {138this.output = new RingBuffer(p_data[0], p_data[0].length, false);139} else if (p_cmd === 'chunk') {140this.output.write(p_data);141}142}143144static array_has_data(arr) {145return arr.length && arr[0].length && arr[0][0].length;146}147148process(inputs, outputs, parameters) {149if (!this.running) {150return false; // Stop processing.151}152if (this.output === null) {153return true; // Not ready yet, keep processing.154}155const process_input = GodotProcessor.array_has_data(inputs);156if (process_input) {157const input = inputs[0];158const chunk = input[0].length * input.length;159if (this.input_buffer.length !== chunk) {160this.input_buffer = new Float32Array(chunk);161}162if (!this.threads) {163GodotProcessor.write_input(this.input_buffer, input);164this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer });165} else if (this.input.space_left() >= chunk) {166GodotProcessor.write_input(this.input_buffer, input);167this.input.write(this.input_buffer);168} else {169// this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.170}171}172const process_output = GodotProcessor.array_has_data(outputs);173if (process_output) {174const output = outputs[0];175const chunk = output[0].length * output.length;176if (this.output_buffer.length !== chunk) {177this.output_buffer = new Float32Array(chunk);178}179if (this.output.data_left() >= chunk) {180this.output.read(this.output_buffer);181GodotProcessor.write_output(output, this.output_buffer);182if (!this.threads) {183this.port.postMessage({ 'cmd': 'read', 'data': chunk });184}185} else {186// this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.187}188}189this.process_notify();190return true;191}192193static write_output(dest, source) {194const channels = dest.length;195for (let ch = 0; ch < channels; ch++) {196for (let sample = 0; sample < dest[ch].length; sample++) {197dest[ch][sample] = source[sample * channels + ch];198}199}200}201202static write_input(dest, source) {203const channels = source.length;204for (let ch = 0; ch < channels; ch++) {205for (let sample = 0; sample < source[ch].length; sample++) {206dest[sample * channels + ch] = source[ch][sample];207}208}209}210}211212registerProcessor('godot-processor', GodotProcessor);213214215