diff options
Diffstat (limited to 'platform/javascript/js/libs')
-rw-r--r-- | platform/javascript/js/libs/audio.worklet.js | 49 | ||||
-rw-r--r-- | platform/javascript/js/libs/library_godot_audio.js | 118 |
2 files changed, 152 insertions, 15 deletions
diff --git a/platform/javascript/js/libs/audio.worklet.js b/platform/javascript/js/libs/audio.worklet.js index df475ba52d..52b3aedf8c 100644 --- a/platform/javascript/js/libs/audio.worklet.js +++ b/platform/javascript/js/libs/audio.worklet.js @@ -29,15 +29,16 @@ /*************************************************************************/ class RingBuffer { - constructor(p_buffer, p_state) { + constructor(p_buffer, p_state, p_threads) { this.buffer = p_buffer; this.avail = p_state; + this.threads = p_threads; this.rpos = 0; this.wpos = 0; } data_left() { - return Atomics.load(this.avail, 0); + return this.threads ? Atomics.load(this.avail, 0) : this.avail; } space_left() { @@ -55,10 +56,16 @@ class RingBuffer { to_write -= high; this.rpos = 0; } - output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); + if (to_write) { + output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); + } this.rpos += to_write; - Atomics.add(this.avail, 0, -output.length); - Atomics.notify(this.avail, 0); + if (this.threads) { + Atomics.add(this.avail, 0, -output.length); + Atomics.notify(this.avail, 0); + } else { + this.avail -= output.length; + } } write(p_buffer) { @@ -77,14 +84,19 @@ class RingBuffer { this.buffer.set(low); this.wpos = low.length; } - Atomics.add(this.avail, 0, to_write); - Atomics.notify(this.avail, 0); + if (this.threads) { + Atomics.add(this.avail, 0, to_write); + Atomics.notify(this.avail, 0); + } else { + this.avail += to_write; + } } } class GodotProcessor extends AudioWorkletProcessor { constructor() { super(); + this.threads = false; this.running = true; this.lock = null; this.notifier = null; @@ -100,24 +112,31 @@ class GodotProcessor extends AudioWorkletProcessor { } process_notify() { - Atomics.add(this.notifier, 0, 1); - Atomics.notify(this.notifier, 0); + if (this.notifier) { + Atomics.add(this.notifier, 0, 1); + Atomics.notify(this.notifier, 0); + } } parse_message(p_cmd, p_data) { if (p_cmd === 'start' && p_data) { const state = p_data[0]; let idx = 0; + this.threads = true; 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); - this.input = new RingBuffer(p_data[1], avail_in); - this.output = new RingBuffer(p_data[2], avail_out); + this.input = new RingBuffer(p_data[1], avail_in, true); + this.output = new RingBuffer(p_data[2], avail_out, true); } else if (p_cmd === 'stop') { this.running = false; this.output = null; this.input = null; + } 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); } } @@ -139,7 +158,10 @@ class GodotProcessor extends AudioWorkletProcessor { if (this.input_buffer.length !== chunk) { this.input_buffer = new Float32Array(chunk); } - if (this.input.space_left() >= chunk) { + 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) { GodotProcessor.write_input(this.input_buffer, input); this.input.write(this.input_buffer); } else { @@ -156,6 +178,9 @@ class GodotProcessor extends AudioWorkletProcessor { if (this.output.data_left() >= chunk) { this.output.read(this.output_buffer); GodotProcessor.write_output(output, this.output_buffer); + if (!this.threads) { + this.port.postMessage({ 'cmd': 'read', 'data': chunk }); + } } else { this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); } diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index c9dae1a7af..f6010fd12a 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -159,6 +159,16 @@ const GodotAudio = { return 1; }, + godot_audio_has_worklet__sig: 'i', + godot_audio_has_worklet: function () { + return (GodotAudio.ctx && GodotAudio.ctx.audioWorklet) ? 1 : 0; + }, + + godot_audio_has_script_processor__sig: 'i', + godot_audio_has_script_processor: function () { + return (GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor) ? 1 : 0; + }, + godot_audio_init__sig: 'iiiii', godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) { const statechange = GodotRuntime.get_func(p_state_change); @@ -209,6 +219,7 @@ const GodotAudioWorklet = { $GodotAudioWorklet: { promise: null, worklet: null, + ring_buffer: null, create: function (channels) { const path = GodotConfig.locate_file('godot.audio.worklet.js'); @@ -239,6 +250,86 @@ const GodotAudioWorklet = { }); }, + start_no_threads: function (p_out_buf, p_out_size, out_callback, p_in_buf, p_in_size, in_callback) { + function RingBuffer() { + let wpos = 0; + let rpos = 0; + let pending_samples = 0; + const wbuf = new Float32Array(p_out_size); + + function send(port) { + if (pending_samples === 0) { + return; + } + const buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); + const size = buffer.length; + const tot_sent = pending_samples; + out_callback(wpos, pending_samples); + if (wpos + pending_samples >= size) { + const high = size - wpos; + wbuf.set(buffer.subarray(wpos, size)); + pending_samples -= high; + wpos = 0; + } + if (pending_samples > 0) { + wbuf.set(buffer.subarray(wpos, wpos + pending_samples), tot_sent - pending_samples); + } + port.postMessage({ 'cmd': 'chunk', 'data': wbuf.subarray(0, tot_sent) }); + wpos += pending_samples; + pending_samples = 0; + } + this.receive = function (recv_buf) { + const buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); + const from = rpos; + let to_write = recv_buf.length; + let high = 0; + if (rpos + to_write >= p_in_size) { + high = p_in_size - rpos; + buffer.set(recv_buf.subarray(0, high), rpos); + to_write -= high; + rpos = 0; + } + if (to_write) { + buffer.set(recv_buf.subarray(high, to_write), rpos); + } + in_callback(from, recv_buf.length); + rpos += to_write; + }; + this.consumed = function (size, port) { + pending_samples += size; + send(port); + }; + } + GodotAudioWorklet.ring_buffer = new RingBuffer(); + GodotAudioWorklet.promise.then(function () { + const node = GodotAudioWorklet.worklet; + const buffer = GodotRuntime.heapSlice(HEAPF32, p_out_buf, p_out_size); + node.connect(GodotAudio.ctx.destination); + node.port.postMessage({ + 'cmd': 'start_nothreads', + 'data': [buffer, p_in_size], + }); + node.port.onmessage = function (event) { + if (!GodotAudioWorklet.worklet) { + return; + } + if (event.data['cmd'] === 'read') { + const read = event.data['data']; + GodotAudioWorklet.ring_buffer.consumed(read, GodotAudioWorklet.worklet.port); + } else if (event.data['cmd'] === 'input') { + const buf = event.data['data']; + if (buf.length > p_in_size) { + GodotRuntime.error('Input chunk is too big'); + return; + } + GodotAudioWorklet.ring_buffer.receive(buf); + } else { + GodotRuntime.error(event.data); + } + }; + }); + }, + get_node: function () { return GodotAudioWorklet.worklet; }, @@ -262,9 +353,15 @@ const GodotAudioWorklet = { }, }, - godot_audio_worklet_create__sig: 'vi', + godot_audio_worklet_create__sig: 'ii', godot_audio_worklet_create: function (channels) { - GodotAudioWorklet.create(channels); + try { + GodotAudioWorklet.create(channels); + } catch (e) { + GodotRuntime.error('Error starting AudioDriverWorklet', e); + return 1; + } + return 0; }, godot_audio_worklet_start__sig: 'viiiii', @@ -275,6 +372,13 @@ const GodotAudioWorklet = { GodotAudioWorklet.start(in_buffer, out_buffer, state); }, + godot_audio_worklet_start_no_threads__sig: 'viiiiii', + godot_audio_worklet_start_no_threads: function (p_out_buf, p_out_size, p_out_callback, p_in_buf, p_in_size, p_in_callback) { + const out_callback = GodotRuntime.get_func(p_out_callback); + const in_callback = GodotRuntime.get_func(p_in_callback); + GodotAudioWorklet.start_no_threads(p_out_buf, p_out_size, out_callback, p_in_buf, p_in_size, in_callback); + }, + godot_audio_worklet_state_wait__sig: 'iiii', godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) { Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); @@ -358,7 +462,15 @@ const GodotAudioScript = { godot_audio_script_create__sig: 'iii', godot_audio_script_create: function (buffer_length, channel_count) { - return GodotAudioScript.create(buffer_length, channel_count); + const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32'); + try { + const out_len = GodotAudioScript.create(buf_len, channel_count); + GodotRuntime.setHeapValue(buffer_length, out_len, 'i32'); + } catch (e) { + GodotRuntime.error('Error starting AudioDriverScriptProcessor', e); + return 1; + } + return 0; }, godot_audio_script_start__sig: 'viiiii', |