mirror of
https://github.com/godotengine/godot.git
synced 2024-11-26 22:23:04 +00:00
Add Web MIDI support
This commit is contained in:
parent
0c45ace151
commit
1ee098ddd7
@ -58,6 +58,7 @@
|
||||
[/csharp]
|
||||
[/codeblocks]
|
||||
[b]Note:[/b] Godot does not support MIDI output, so there is no way to emit MIDI messages from Godot. Only MIDI input is supported.
|
||||
[b]Note:[/b] On the Web platform, using MIDI input requires a browser permission to be granted first. This permission request is performed when calling [method OS.open_midi_inputs]. MIDI input will not work until the user accepts the permission request.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="MIDI Message Status Byte List">https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes</link>
|
||||
|
@ -23,7 +23,7 @@
|
||||
<return type="void" />
|
||||
<description>
|
||||
Shuts down the system MIDI driver. Godot will no longer receive [InputEventMIDI]. See also [method open_midi_inputs] and [method get_connected_midi_inputs].
|
||||
[b]Note:[/b] This method is implemented on Linux, macOS, and Windows.
|
||||
[b]Note:[/b] This method is implemented on Linux, macOS, Web, and Windows.
|
||||
</description>
|
||||
</method>
|
||||
<method name="crash">
|
||||
@ -244,7 +244,8 @@
|
||||
<return type="PackedStringArray" />
|
||||
<description>
|
||||
Returns an array of connected MIDI device names, if they exist. Returns an empty array if the system MIDI driver has not previously been initialized with [method open_midi_inputs]. See also [method close_midi_inputs].
|
||||
[b]Note:[/b] This method is implemented on Linux, macOS, and Windows.
|
||||
[b]Note:[/b] This method is implemented on Linux, macOS, Web, and Windows.
|
||||
[b]Note:[/b] On the Web platform, using MIDI input requires a browser permission to be granted first. This permission request is performed when calling [method open_midi_inputs]. The browser will refrain from processing MIDI input until the user accepts the permission request.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_data_dir" qualifiers="const">
|
||||
@ -698,7 +699,8 @@
|
||||
<return type="void" />
|
||||
<description>
|
||||
Initializes the singleton for the system MIDI driver, allowing Godot to receive [InputEventMIDI]. See also [method get_connected_midi_inputs] and [method close_midi_inputs].
|
||||
[b]Note:[/b] This method is implemented on Linux, macOS, and Windows.
|
||||
[b]Note:[/b] This method is implemented on Linux, macOS, Web, and Windows.
|
||||
[b]Note:[/b] On the Web platform, using MIDI input requires a browser permission to be granted first. This permission request is performed when calling [method open_midi_inputs]. The browser will refrain from processing MIDI input until the user accepts the permission request.
|
||||
</description>
|
||||
</method>
|
||||
<method name="read_buffer_from_stdin">
|
||||
|
@ -22,6 +22,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS:
|
||||
|
||||
web_files = [
|
||||
"audio_driver_web.cpp",
|
||||
"webmidi_driver.cpp",
|
||||
"display_server_web.cpp",
|
||||
"http_client_web.cpp",
|
||||
"javascript_bridge_singleton.cpp",
|
||||
@ -38,6 +39,7 @@ sys_env.AddJSLibraries(
|
||||
"js/libs/library_godot_audio.js",
|
||||
"js/libs/library_godot_display.js",
|
||||
"js/libs/library_godot_fetch.js",
|
||||
"js/libs/library_godot_webmidi.js",
|
||||
"js/libs/library_godot_os.js",
|
||||
"js/libs/library_godot_runtime.js",
|
||||
"js/libs/library_godot_input.js",
|
||||
|
51
platform/web/godot_midi.h
Normal file
51
platform/web/godot_midi.h
Normal file
@ -0,0 +1,51 @@
|
||||
/**************************************************************************/
|
||||
/* godot_midi.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef GODOT_MIDI_H
|
||||
#define GODOT_MIDI_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
extern int godot_js_webmidi_open_midi_inputs(
|
||||
void (*p_callback)(int p_size, const char **p_connected_input_names),
|
||||
void (*p_on_midi_message)(int p_device_index, int p_status, const uint8_t *p_data, int p_data_len),
|
||||
const uint8_t *p_data_buffer,
|
||||
const int p_data_buffer_len);
|
||||
|
||||
extern void godot_js_webmidi_close_midi_inputs();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // GODOT_MIDI_H
|
94
platform/web/js/libs/library_godot_webmidi.js
Normal file
94
platform/web/js/libs/library_godot_webmidi.js
Normal 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);
|
@ -32,6 +32,7 @@
|
||||
#define OS_WEB_H
|
||||
|
||||
#include "audio_driver_web.h"
|
||||
#include "webmidi_driver.h"
|
||||
|
||||
#include "godot_js.h"
|
||||
|
||||
@ -45,6 +46,8 @@ class OS_Web : public OS_Unix {
|
||||
MainLoop *main_loop = nullptr;
|
||||
List<AudioDriverWeb *> audio_drivers;
|
||||
|
||||
MIDIDriverWebMidi midi_driver;
|
||||
|
||||
bool idb_is_syncing = false;
|
||||
bool idb_available = false;
|
||||
bool idb_needs_sync = false;
|
||||
|
97
platform/web/webmidi_driver.cpp
Normal file
97
platform/web/webmidi_driver.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
/**************************************************************************/
|
||||
/* webmidi_driver.cpp */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "webmidi_driver.h"
|
||||
|
||||
#ifdef PROXY_TO_PTHREAD_ENABLED
|
||||
#include "core/object/callable_method_pointer.h"
|
||||
#endif
|
||||
MIDIDriverWebMidi *MIDIDriverWebMidi::get_singleton() {
|
||||
return static_cast<MIDIDriverWebMidi *>(MIDIDriver::get_singleton());
|
||||
}
|
||||
|
||||
Error MIDIDriverWebMidi::open() {
|
||||
Error error = (Error)godot_js_webmidi_open_midi_inputs(&MIDIDriverWebMidi::set_input_names_callback, &MIDIDriverWebMidi::on_midi_message, _event_buffer, MIDIDriverWebMidi::MAX_EVENT_BUFFER_LENGTH);
|
||||
if (error == ERR_UNAVAILABLE) {
|
||||
ERR_PRINT("Web MIDI is not supported on this browser");
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::close() {
|
||||
get_singleton()->connected_input_names.clear();
|
||||
godot_js_webmidi_close_midi_inputs();
|
||||
}
|
||||
|
||||
MIDIDriverWebMidi::~MIDIDriverWebMidi() {
|
||||
close();
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::set_input_names_callback(int p_size, const char **p_input_names) {
|
||||
Vector<String> input_names;
|
||||
for (int i = 0; i < p_size; i++) {
|
||||
input_names.append(String::utf8(p_input_names[i]));
|
||||
}
|
||||
#ifdef PROXY_TO_PTHREAD_ENABLED
|
||||
if (!Thread::is_main_thread()) {
|
||||
callable_mp_static(MIDIDriverWebMidi::_set_input_names_callback).call_deferred(input_names);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
_set_input_names_callback(input_names);
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::_set_input_names_callback(const Vector<String> &p_input_names) {
|
||||
get_singleton()->connected_input_names.clear();
|
||||
for (int i = 0; i < p_input_names.size(); i++) {
|
||||
get_singleton()->connected_input_names.push_back(p_input_names[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::on_midi_message(int p_device_index, int p_status, const uint8_t *p_data, int p_data_len) {
|
||||
PackedByteArray data;
|
||||
data.resize(p_data_len);
|
||||
uint8_t *data_ptr = data.ptrw();
|
||||
for (int i = 0; i < p_data_len; i++) {
|
||||
data_ptr[i] = p_data[i];
|
||||
}
|
||||
#ifdef PROXY_TO_PTHREAD_ENABLED
|
||||
if (!Thread::is_main_thread()) {
|
||||
callable_mp_static(MIDIDriverWebMidi::_on_midi_message).call_deferred(p_device_index, p_status, data, p_data_len);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
_on_midi_message(p_device_index, p_status, data, p_data_len);
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::_on_midi_message(int p_device_index, int p_status, const PackedByteArray &p_data, int p_data_len) {
|
||||
MIDIDriver::send_event(p_device_index, p_status, p_data.ptr(), p_data_len);
|
||||
}
|
60
platform/web/webmidi_driver.h
Normal file
60
platform/web/webmidi_driver.h
Normal file
@ -0,0 +1,60 @@
|
||||
/**************************************************************************/
|
||||
/* webmidi_driver.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef WEBMIDI_DRIVER_H
|
||||
#define WEBMIDI_DRIVER_H
|
||||
#include "core/os/midi_driver.h"
|
||||
|
||||
#include "godot_js.h"
|
||||
#include "godot_midi.h"
|
||||
|
||||
class MIDIDriverWebMidi : public MIDIDriver {
|
||||
private:
|
||||
static const int MAX_EVENT_BUFFER_LENGTH = 2;
|
||||
uint8_t _event_buffer[MAX_EVENT_BUFFER_LENGTH];
|
||||
|
||||
public:
|
||||
// Override return type to make writing static callbacks less tedious.
|
||||
static MIDIDriverWebMidi *get_singleton();
|
||||
|
||||
virtual Error open() override;
|
||||
virtual void close() override final;
|
||||
|
||||
MIDIDriverWebMidi() = default;
|
||||
virtual ~MIDIDriverWebMidi();
|
||||
|
||||
WASM_EXPORT static void set_input_names_callback(int p_size, const char **p_input_names);
|
||||
static void _set_input_names_callback(const Vector<String> &p_input_names);
|
||||
|
||||
WASM_EXPORT static void on_midi_message(int p_device_index, int p_status, const uint8_t *p_data, int p_data_len);
|
||||
static void _on_midi_message(int p_device_index, int p_status, const PackedByteArray &p_data, int p_data_len);
|
||||
};
|
||||
|
||||
#endif // WEBMIDI_DRIVER_H
|
Loading…
Reference in New Issue
Block a user