feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -0,0 +1,69 @@
/**************************************************************************/
/* godot.audio.position.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. */
/**************************************************************************/
const POST_THRESHOLD_S = 0.1;
class GodotPositionReportingProcessor extends AudioWorkletProcessor {
constructor(...args) {
super(...args);
this.lastPostTime = currentTime;
this.position = 0;
this.ended = false;
this.port.onmessage = (event) => {
if (event?.data?.type === 'ended') {
this.ended = true;
}
};
}
process(inputs, _outputs, _parameters) {
if (this.ended) {
return false;
}
if (inputs.length > 0) {
const input = inputs[0];
if (input.length > 0) {
this.position += input[0].length;
}
}
// Posting messages is expensive. Let's limit the number of posts.
if (currentTime - this.lastPostTime > POST_THRESHOLD_S) {
this.lastPostTime = currentTime;
this.port.postMessage({ type: 'position', data: this.position });
}
return true;
}
}
registerProcessor('godot-position-reporting-processor', GodotPositionReportingProcessor);

View file

@ -77,7 +77,7 @@ class Sample {
* Creates a `Sample` based on the params. Will register it to the
* `GodotAudio.samples` registry.
* @param {SampleParams} params Base params
* @param {SampleOptions} [options={{}}] Optional params
* @param {SampleOptions | undefined} options Optional params.
* @returns {Sample}
*/
static create(params, options = {}) {
@ -98,7 +98,7 @@ class Sample {
/**
* `Sample` constructor.
* @param {SampleParams} params Base params
* @param {SampleOptions} [options={{}}] Optional params
* @param {SampleOptions | undefined} options Optional params.
*/
constructor(params, options = {}) {
/** @type {string} */
@ -328,8 +328,10 @@ class SampleNodeBus {
* offset?: number
* playbackRate?: number
* startTime?: number
* pitchScale?: number
* loopMode?: LoopMode
* volume?: Float32Array
* start?: boolean
* }} SampleNodeOptions
*/
@ -391,7 +393,7 @@ class SampleNode {
* Creates a `SampleNode` based on the params. Will register the `SampleNode` to
* the `GodotAudio.sampleNodes` regisery.
* @param {SampleNodeParams} params Base params.
* @param {SampleNodeOptions} options Optional params.
* @param {SampleNodeOptions | undefined} options Optional params.
* @returns {SampleNode}
*/
static create(params, options = {}) {
@ -411,7 +413,7 @@ class SampleNode {
/**
* @param {SampleNodeParams} params Base params
* @param {SampleNodeOptions} [options={{}}] Optional params
* @param {SampleNodeOptions | undefined} options Optional params.
*/
constructor(params, options = {}) {
/** @type {string} */
@ -421,9 +423,15 @@ class SampleNode {
/** @type {number} */
this.offset = options.offset ?? 0;
/** @type {number} */
this._playbackPosition = options.offset;
/** @type {number} */
this.startTime = options.startTime ?? 0;
/** @type {boolean} */
this.isPaused = false;
/** @type {boolean} */
this.isStarted = false;
/** @type {boolean} */
this.isCanceled = false;
/** @type {number} */
this.pauseTime = 0;
/** @type {number} */
@ -431,7 +439,7 @@ class SampleNode {
/** @type {LoopMode} */
this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
/** @type {number} */
this._pitchScale = 1;
this._pitchScale = options.pitchScale ?? 1;
/** @type {number} */
this._sourceStartTime = 0;
/** @type {Map<Bus, SampleNodeBus>} */
@ -440,6 +448,8 @@ class SampleNode {
this._source = GodotAudio.ctx.createBufferSource();
this._onended = null;
/** @type {AudioWorkletNode | null} */
this._positionWorklet = null;
this.setPlaybackRate(options.playbackRate ?? 44100);
this._source.buffer = this.getSample().getAudioBuffer();
@ -449,6 +459,12 @@ class SampleNode {
const bus = GodotAudio.Bus.getBus(params.busIndex);
const sampleNodeBus = this.getSampleNodeBus(bus);
sampleNodeBus.setVolume(options.volume);
this.connectPositionWorklet(options.start).catch((err) => {
const newErr = new Error('Failed to create PositionWorklet.');
newErr.cause = err;
GodotRuntime.error(newErr);
});
}
/**
@ -459,6 +475,14 @@ class SampleNode {
return this._playbackRate;
}
/**
* Gets the playback position.
* @returns {number}
*/
getPlaybackPosition() {
return this._playbackPosition;
}
/**
* Sets the playback rate.
* @param {number} val Value to set.
@ -508,8 +532,12 @@ class SampleNode {
* @returns {void}
*/
start() {
if (this.isStarted) {
return;
}
this._resetSourceStartTime();
this._source.start(this.startTime, this.offset);
this.isStarted = true;
}
/**
@ -584,18 +612,60 @@ class SampleNode {
return this._sampleNodeBuses.get(bus);
}
/**
* Sets up and connects the source to the GodotPositionReportingProcessor
* If the worklet module is not loaded in, it will be added
*/
async connectPositionWorklet(start) {
await GodotAudio.audioPositionWorkletPromise;
if (this.isCanceled) {
return;
}
this._source.connect(this.getPositionWorklet());
if (start) {
this.start();
}
}
/**
* Get a AudioWorkletProcessor
* @returns {AudioWorkletNode}
*/
getPositionWorklet() {
if (this._positionWorklet != null) {
return this._positionWorklet;
}
this._positionWorklet = new AudioWorkletNode(
GodotAudio.ctx,
'godot-position-reporting-processor'
);
this._positionWorklet.port.onmessage = (event) => {
switch (event.data['type']) {
case 'position':
this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset;
break;
default:
// Do nothing.
}
};
return this._positionWorklet;
}
/**
* Clears the `SampleNode`.
* @returns {void}
*/
clear() {
this.isCanceled = true;
this.isPaused = false;
this.pauseTime = 0;
if (this._source != null) {
this._source.removeEventListener('ended', this._onended);
this._onended = null;
this._source.stop();
if (this.isStarted) {
this._source.stop();
}
this._source.disconnect();
this._source = null;
}
@ -605,6 +675,13 @@ class SampleNode {
}
this._sampleNodeBuses.clear();
if (this._positionWorklet) {
this._positionWorklet.disconnect();
this._positionWorklet.port.onmessage = null;
this._positionWorklet.port.postMessage({ type: 'ended' });
this._positionWorklet = null;
}
GodotAudio.SampleNode.delete(this.id);
}
@ -645,7 +722,12 @@ class SampleNode {
const pauseTime = this.isPaused
? this.pauseTime
: 0;
if (this._positionWorklet != null) {
this._positionWorklet.port.postMessage({ type: 'clear' });
this._source.connect(this._positionWorklet);
}
this._source.start(this.startTime, this.offset + pauseTime);
this.isStarted = true;
}
/**
@ -653,6 +735,9 @@ class SampleNode {
* @returns {void}
*/
_pause() {
if (!this.isStarted) {
return;
}
this.isPaused = true;
this.pauseTime = (GodotAudio.ctx.currentTime - this._sourceStartTime) / this.getPlaybackRate();
this._source.stop();
@ -780,7 +865,10 @@ class Bus {
* @returns {void}
*/
static move(fromIndex, toIndex) {
const movedBus = GodotAudio.Bus.getBus(fromIndex);
const movedBus = GodotAudio.Bus.getBusOrNull(fromIndex);
if (movedBus == null) {
return;
}
const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
// Inserts at index.
buses.splice(toIndex - 1, 0, movedBus);
@ -1108,6 +1196,9 @@ const _GodotAudio = {
driver: null,
interval: 0,
/** @type {Promise} */
audioPositionWorkletPromise: null,
/**
* Converts linear volume to Db.
* @param {number} linear Linear value to convert.
@ -1174,6 +1265,10 @@ const _GodotAudio = {
onlatencyupdate(computed_latency);
}, 1000);
GodotOS.atexit(GodotAudio.close_async);
const path = GodotConfig.locate_file('godot.audio.position.worklet.js');
GodotAudio.audioPositionWorkletPromise = ctx.audioWorklet.addModule(path);
return ctx.destination.channelCount;
},
@ -1252,7 +1347,7 @@ const _GodotAudio = {
* @param {string} playbackObjectId The unique id of the sample playback
* @param {string} streamObjectId The unique id of the stream
* @param {number} busIndex Index of the bus currently binded to the sample playback
* @param {SampleNodeOptions} startOptions Optional params
* @param {SampleNodeOptions | undefined} startOptions Optional params.
* @returns {void}
*/
start_sample: function (
@ -1262,7 +1357,7 @@ const _GodotAudio = {
startOptions
) {
GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
const sampleNode = GodotAudio.SampleNode.create(
GodotAudio.SampleNode.create(
{
busIndex,
id: playbackObjectId,
@ -1270,7 +1365,6 @@ const _GodotAudio = {
},
startOptions
);
sampleNode.start();
},
/**
@ -1337,7 +1431,10 @@ const _GodotAudio = {
* @returns {void}
*/
remove_sample_bus: function (index) {
const bus = GodotAudio.Bus.getBus(index);
const bus = GodotAudio.Bus.getBusOrNull(index);
if (bus == null) {
return;
}
bus.clear();
},
@ -1367,8 +1464,17 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_send: function (busIndex, sendIndex) {
const bus = GodotAudio.Bus.getBus(busIndex);
bus.setSend(GodotAudio.Bus.getBus(sendIndex));
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
if (bus == null) {
// Cannot send from an invalid bus.
return;
}
let targetBus = GodotAudio.Bus.getBusOrNull(sendIndex);
if (targetBus == null) {
// Send to master.
targetBus = GodotAudio.Bus.getBus(0);
}
bus.setSend(targetBus);
},
/**
@ -1378,7 +1484,10 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_volume_db: function (busIndex, volumeDb) {
const bus = GodotAudio.Bus.getBus(busIndex);
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
if (bus == null) {
return;
}
bus.setVolumeDb(volumeDb);
},
@ -1389,7 +1498,10 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_solo: function (busIndex, enable) {
const bus = GodotAudio.Bus.getBus(busIndex);
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
if (bus == null) {
return;
}
bus.solo(enable);
},
@ -1400,7 +1512,10 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_mute: function (busIndex, enable) {
const bus = GodotAudio.Bus.getBus(busIndex);
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
if (bus == null) {
return;
}
bus.mute(enable);
},
},
@ -1562,13 +1677,14 @@ const _GodotAudio = {
},
godot_audio_sample_start__proxy: 'sync',
godot_audio_sample_start__sig: 'viiiii',
godot_audio_sample_start__sig: 'viiiifi',
/**
* Starts a sample.
* @param {number} playbackObjectIdStrPtr Playback object id pointer
* @param {number} streamObjectIdStrPtr Stream object id pointer
* @param {number} busIndex Bus index
* @param {number} offset Sample offset
* @param {number} pitchScale Pitch scale
* @param {number} volumePtr Volume pointer
* @returns {void}
*/
@ -1577,6 +1693,7 @@ const _GodotAudio = {
streamObjectIdStrPtr,
busIndex,
offset,
pitchScale,
volumePtr
) {
/** @type {string} */
@ -1585,11 +1702,13 @@ const _GodotAudio = {
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
/** @type {Float32Array} */
const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8);
/** @type {SampleNodeConstructorOptions} */
/** @type {SampleNodeOptions} */
const startOptions = {
offset,
volume,
playbackRate: 1,
pitchScale,
start: true,
};
GodotAudio.start_sample(
playbackObjectId,
@ -1635,6 +1754,22 @@ const _GodotAudio = {
return Number(GodotAudio.sampleNodes.has(playbackObjectId));
},
godot_audio_get_sample_playback_position__proxy: 'sync',
godot_audio_get_sample_playback_position__sig: 'di',
/**
* Returns the position of the playback position.
* @param {number} playbackObjectIdStrPtr Playback object id pointer
* @returns {number}
*/
godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) {
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
if (sampleNode == null) {
return 0;
}
return sampleNode.getPlaybackPosition();
},
godot_audio_sample_update_pitch_scale__proxy: 'sync',
godot_audio_sample_update_pitch_scale__sig: 'vii',
/**
@ -2029,7 +2164,7 @@ autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
mergeInto(LibraryManager.library, GodotAudioWorklet);
/*
* The ScriptProcessorNode API, used when threads are disabled.
* The ScriptProcessorNode API, used as a fallback if AudioWorklet is not available.
*/
const GodotAudioScript = {
$GodotAudioScript__deps: ['$GodotAudio'],

View file

@ -59,7 +59,12 @@ const GodotFetch = {
});
obj.status = response.status;
obj.response = response;
obj.reader = response.body.getReader();
// `body` can be null per spec (for example, in cases where the request method is HEAD).
// As of the time of writing, Chromium (127.0.6533.72) does not follow the spec but Firefox (131.0.3) does.
// See godotengine/godot#76825 for more information.
// See Chromium revert (of the change to follow the spec):
// https://chromium.googlesource.com/chromium/src/+/135354b7bdb554cd03c913af7c90aceead03c4d4
obj.reader = response.body?.getReader();
obj.chunked = chunked;
},
@ -121,6 +126,10 @@ const GodotFetch = {
}
obj.reading = true;
obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
} else if (obj.reader == null && obj.response.body == null) {
// Emulate a stream closure to maintain the request lifecycle.
obj.reading = true;
GodotFetch.onread(id, { value: undefined, done: true });
}
},
},
@ -159,7 +168,10 @@ const GodotFetch = {
if (!obj.response) {
return 0;
}
if (obj.reader) {
// If the reader is nullish, but there is no body, and the request is not marked as done,
// the same status should be returned as though the request is currently being read
// so that the proper lifecycle closure can be handled in `read()`.
if (obj.reader || (obj.response.body == null && !obj.done)) {
return 1;
}
if (obj.done) {

View file

@ -38,41 +38,57 @@ const GodotIME = {
$GodotIME: {
ime: null,
active: false,
focusTimerIntervalId: -1,
getModifiers: function (evt) {
return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);
},
ime_active: function (active) {
function focus_timer() {
GodotIME.active = true;
function clearFocusTimerInterval() {
clearInterval(GodotIME.focusTimerIntervalId);
GodotIME.focusTimerIntervalId = -1;
}
function focusTimer() {
if (GodotIME.ime == null) {
clearFocusTimerInterval();
return;
}
GodotIME.ime.focus();
}
if (GodotIME.ime) {
if (active) {
GodotIME.ime.style.display = 'block';
setInterval(focus_timer, 100);
} else {
GodotIME.ime.style.display = 'none';
GodotConfig.canvas.focus();
GodotIME.active = false;
}
if (GodotIME.focusTimerIntervalId > -1) {
clearFocusTimerInterval();
}
if (GodotIME.ime == null) {
return;
}
GodotIME.active = active;
if (active) {
GodotIME.ime.style.display = 'block';
GodotIME.focusTimerIntervalId = setInterval(focusTimer, 100);
} else {
GodotIME.ime.style.display = 'none';
GodotConfig.canvas.focus();
}
},
ime_position: function (x, y) {
if (GodotIME.ime) {
const canvas = GodotConfig.canvas;
const rect = canvas.getBoundingClientRect();
const rw = canvas.width / rect.width;
const rh = canvas.height / rect.height;
const clx = (x / rw) + rect.x;
const cly = (y / rh) + rect.y;
GodotIME.ime.style.left = `${clx}px`;
GodotIME.ime.style.top = `${cly}px`;
if (GodotIME.ime == null) {
return;
}
const canvas = GodotConfig.canvas;
const rect = canvas.getBoundingClientRect();
const rw = canvas.width / rect.width;
const rh = canvas.height / rect.height;
const clx = (x / rw) + rect.x;
const cly = (y / rh) + rect.y;
GodotIME.ime.style.left = `${clx}px`;
GodotIME.ime.style.top = `${cly}px`;
},
init: function (ime_cb, key_cb, code, key) {
@ -84,20 +100,27 @@ const GodotIME = {
evt.preventDefault();
}
function ime_event_cb(event) {
if (GodotIME.ime) {
if (event.type === 'compositionstart') {
ime_cb(0, null);
GodotIME.ime.innerHTML = '';
} else if (event.type === 'compositionupdate') {
const ptr = GodotRuntime.allocString(event.data);
ime_cb(1, ptr);
GodotRuntime.free(ptr);
} else if (event.type === 'compositionend') {
const ptr = GodotRuntime.allocString(event.data);
ime_cb(2, ptr);
GodotRuntime.free(ptr);
GodotIME.ime.innerHTML = '';
}
if (GodotIME.ime == null) {
return;
}
switch (event.type) {
case 'compositionstart':
ime_cb(0, null);
GodotIME.ime.innerHTML = '';
break;
case 'compositionupdate': {
const ptr = GodotRuntime.allocString(event.data);
ime_cb(1, ptr);
GodotRuntime.free(ptr);
} break;
case 'compositionend': {
const ptr = GodotRuntime.allocString(event.data);
ime_cb(2, ptr);
GodotRuntime.free(ptr);
GodotIME.ime.innerHTML = '';
} break;
default:
// Do nothing.
}
}
@ -133,10 +156,15 @@ const GodotIME = {
},
clear: function () {
if (GodotIME.ime) {
GodotIME.ime.remove();
GodotIME.ime = null;
if (GodotIME.ime == null) {
return;
}
if (GodotIME.focusTimerIntervalId > -1) {
clearInterval(GodotIME.focusTimerIntervalId);
GodotIME.focusTimerIntervalId = -1;
}
GodotIME.ime.remove();
GodotIME.ime = null;
},
},
};

View file

@ -127,6 +127,10 @@ const GodotJSWrapper = {
GodotRuntime.setHeapValue(p_exchange, id, 'i64');
return 24; // OBJECT
},
isBuffer: function (obj) {
return obj instanceof ArrayBuffer || ArrayBuffer.isView(obj);
},
},
godot_js_wrapper_interface_get__proxy: 'sync',
@ -303,6 +307,34 @@ const GodotJSWrapper = {
return -1;
}
},
godot_js_wrapper_object_is_buffer__proxy: 'sync',
godot_js_wrapper_object_is_buffer__sig: 'ii',
godot_js_wrapper_object_is_buffer: function (p_id) {
const obj = GodotJSWrapper.get_proxied_value(p_id);
return GodotJSWrapper.isBuffer(obj)
? 1
: 0;
},
godot_js_wrapper_object_transfer_buffer__proxy: 'sync',
godot_js_wrapper_object_transfer_buffer__sig: 'viiii',
godot_js_wrapper_object_transfer_buffer: function (p_id, p_byte_arr, p_byte_arr_write, p_callback) {
let obj = GodotJSWrapper.get_proxied_value(p_id);
if (!GodotJSWrapper.isBuffer(obj)) {
return;
}
if (ArrayBuffer.isView(obj) && !(obj instanceof Uint8Array)) {
obj = new Uint8Array(obj.buffer);
} else if (obj instanceof ArrayBuffer) {
obj = new Uint8Array(obj);
}
const resizePackedByteArrayAndOpenWrite = GodotRuntime.get_func(p_callback);
const bytesPtr = resizePackedByteArrayAndOpenWrite(p_byte_arr, p_byte_arr_write, obj.length);
HEAPU8.set(obj, bytesPtr);
},
};
autoAddDeps(GodotJSWrapper, '$GodotJSWrapper');

View file

@ -441,8 +441,12 @@ const GodotPWA = {
godot_js_pwa_cb__sig: 'vi',
godot_js_pwa_cb: function (p_update_cb) {
if ('serviceWorker' in navigator) {
const cb = GodotRuntime.get_func(p_update_cb);
navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
try {
const cb = GodotRuntime.get_func(p_update_cb);
navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
} catch (e) {
GodotRuntime.error('Failed to assign PWA callback', e);
}
}
},
@ -450,12 +454,17 @@ const GodotPWA = {
godot_js_pwa_update__sig: 'i',
godot_js_pwa_update: function () {
if ('serviceWorker' in navigator && GodotPWA.hasUpdate) {
navigator.serviceWorker.getRegistration().then(function (reg) {
if (!reg || !reg.waiting) {
return;
}
reg.waiting.postMessage('update');
});
try {
navigator.serviceWorker.getRegistration().then(function (reg) {
if (!reg || !reg.waiting) {
return;
}
reg.waiting.postMessage('update');
});
} catch (e) {
GodotRuntime.error(e);
return 1;
}
return 0;
}
return 1;

View file

@ -0,0 +1,94 @@
/**************************************************************************/
/* library_godot_webmidi.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. */
/**************************************************************************/
const GodotWebMidi = {
$GodotWebMidi__deps: ['$GodotRuntime'],
$GodotWebMidi: {
abortControllers: [],
isListening: false,
},
godot_js_webmidi_open_midi_inputs__deps: ['$GodotWebMidi'],
godot_js_webmidi_open_midi_inputs__proxy: 'sync',
godot_js_webmidi_open_midi_inputs__sig: 'iiii',
godot_js_webmidi_open_midi_inputs: function (pSetInputNamesCb, pOnMidiMessageCb, pDataBuffer, dataBufferLen) {
if (GodotWebMidi.is_listening) {
return 0; // OK
}
if (!navigator.requestMIDIAccess) {
return 2; // ERR_UNAVAILABLE
}
const setInputNamesCb = GodotRuntime.get_func(pSetInputNamesCb);
const onMidiMessageCb = GodotRuntime.get_func(pOnMidiMessageCb);
GodotWebMidi.isListening = true;
navigator.requestMIDIAccess().then((midi) => {
const inputs = [...midi.inputs.values()];
const inputNames = inputs.map((input) => input.name);
const c_ptr = GodotRuntime.allocStringArray(inputNames);
setInputNamesCb(inputNames.length, c_ptr);
GodotRuntime.freeStringArray(c_ptr, inputNames.length);
inputs.forEach((input, i) => {
const abortController = new AbortController();
GodotWebMidi.abortControllers.push(abortController);
input.addEventListener('midimessage', (event) => {
const status = event.data[0];
const data = event.data.slice(1);
const size = data.length;
if (size > dataBufferLen) {
throw new Error(`data too big ${size} > ${dataBufferLen}`);
}
HEAPU8.set(data, pDataBuffer);
onMidiMessageCb(i, status, pDataBuffer, data.length);
}, { signal: abortController.signal });
});
});
return 0; // OK
},
godot_js_webmidi_close_midi_inputs__deps: ['$GodotWebMidi'],
godot_js_webmidi_close_midi_inputs__proxy: 'sync',
godot_js_webmidi_close_midi_inputs__sig: 'v',
godot_js_webmidi_close_midi_inputs: function () {
for (const abortController of GodotWebMidi.abortControllers) {
abortController.abort();
}
GodotWebMidi.abortControllers = [];
GodotWebMidi.isListening = false;
},
};
mergeInto(LibraryManager.library, GodotWebMidi);