Files
2024-10-28 19:24:18 +01:00

304 lines
8.3 KiB
C++

#pragma once
#include <libremidi/backends/emscripten/midi_in.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp>
#include <libremidi/backends/emscripten/observer.hpp>
#include <libremidi/libremidi.hpp>
#include <emscripten.h>
#include <map>
extern "C" {
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_poll();
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_input(int port, double timestamp, int len, char* bytes);
}
namespace libremidi
{
namespace webmidi_helpers
{
class midi_access_emscripten
{
public:
static midi_access_emscripten& instance() noexcept
{
static midi_access_emscripten inst;
return inst;
}
bool available() const noexcept
{
return EM_ASM_INT(return typeof globalThis.__libreMidi_access !== undefined;);
}
int input_count() const noexcept
{
return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;);
}
int output_count() const noexcept
{
return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;);
}
void load_current_infos() noexcept
{
#define get_js_string(variable_to_read, ...) \
(char*)EM_ASM_INT( \
{ \
var jsstr = variable_to_read; \
var bytes = lengthBytesUTF8(jsstr) + 1; \
var str = _malloc(bytes); \
stringToUTF8(jsstr, str, bytes); \
return str; \
}, \
__VA_ARGS__);
EM_ASM_INT({
let inputs = [];
let outputs = [];
for (let inpt of globalThis.__libreMidi_access.inputs.values())
{
inputs.push(inpt);
}
for (let outpt of globalThis.__libreMidi_access.outputs.values())
{
outputs.push(outpt);
}
globalThis.__libreMidi_currentInputs = inputs;
globalThis.__libreMidi_currentOutputs = outputs;
});
const int inputs = input_count();
const int outputs = output_count();
m_current_inputs.resize(inputs);
m_current_outputs.resize(outputs);
for (int i = 0; i < inputs; i++)
{
int device_index = -1;
char* midi_id = get_js_string(globalThis.__libreMidi_currentInputs[$0].id, i);
auto it = m_input_indices.find(midi_id); // TODO transparent comparator, string_view...
if (it == m_input_indices.end())
{
device_index = m_input_indices.size();
m_input_indices[midi_id] = device_index;
}
else
{
device_index = it->second;
}
char* midi_name = get_js_string(globalThis.__libreMidi_currentInputs[$0].name, i);
const bool connected
= EM_ASM_INT(return globalThis.__libreMidi_currentInputs[$0].state === "connected", i);
m_current_inputs[device_index].id = midi_id;
m_current_inputs[device_index].name = midi_name;
m_current_inputs[device_index].connected = connected;
free(midi_id);
free(midi_name);
}
for (int i = 0; i < outputs; i++)
{
int device_index = -1;
char* midi_id = get_js_string(globalThis.__libreMidi_currentOutputs[$0].id, i);
auto it = m_output_indices.find(midi_id); // TODO transparent comparator, string_view...
if (it == m_output_indices.end())
{
device_index = m_output_indices.size();
m_output_indices[midi_id] = device_index;
}
else
{
device_index = it->second;
}
char* midi_name = get_js_string(globalThis.__libreMidi_currentOutputs[$0].name, i);
const bool connected
= EM_ASM_INT(return globalThis.__libreMidi_currentOutputs[$0].state === "connected", i);
m_current_outputs[device_index].id = midi_id;
m_current_outputs[device_index].name = midi_name;
m_current_outputs[device_index].connected = connected;
free(midi_id);
free(midi_name);
}
#undef get_js_string
}
void register_observer(observer_emscripten& obs)
{
m_observers.push_back(&obs);
if (m_observers.size() == 1)
{
start_observing();
}
}
void unregister_observer(observer_emscripten& obs)
{
if (m_observers.size() == 1)
{
stop_observing();
}
auto it = std::find(m_observers.begin(), m_observers.end(), &obs);
if (it != m_observers.end())
{
m_observers.erase(it);
}
}
void devices_poll()
{
load_current_infos();
for (auto& obs : m_observers)
{
obs->update(m_current_inputs, m_current_outputs);
}
}
void open_input(int port_index, midi_in_emscripten& input)
{
auto& vec = m_opened_inputs[port_index];
vec.push_back(&input);
if (vec.size() != 1)
return;
start_stream(port_index);
}
void close_input(int port_index, midi_in_emscripten& input)
{
auto& vec = m_opened_inputs[port_index];
auto it = std::find(vec.begin(), vec.end(), &input);
if (it != vec.end())
{
vec.erase(it);
}
if (vec.empty())
{
stop_stream(port_index);
}
}
void devices_input(int port, double timestamp, int len, char* data)
{
unsigned char* bytes = reinterpret_cast<unsigned char*>(data);
for (auto input : m_opened_inputs[port])
{
input->on_input(timestamp, bytes, bytes + len);
}
}
stdx::error send_message(int port_index, const char* bytes, int len)
{
const auto& id = m_current_outputs[port_index].id;
EM_ASM(
{
let data = HEAPU8.subarray($0, $0 + $1);
const id = UTF8ToString($2);
let output = globalThis.__libreMidi_access.outputs.get(id);
let bytes = HEAPU8.subarray($0, $0 + $1);
output.send(Array.from(bytes));
},
bytes, len, id.c_str());
}
const std::vector<device_information>& inputs() const noexcept { return m_current_inputs; }
const std::vector<device_information>& outputs() const noexcept { return m_current_outputs; }
private:
midi_access_emscripten() noexcept
{
EM_ASM(
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess().then(
(midiAccess) => { globalThis.__libreMidi_access = midiAccess; },
() => console.log('MIDI support rejected, MIDI will not be available;'));
} else { console.log('WebMIDI is not supported in this browser.'); });
}
~midi_access_emscripten() { stop_observing(); }
void start_observing()
{
EM_ASM(
let id = setInterval(Module._libremidi_devices_poll, 100);
globalThis.__libreMidi_timer = id;);
}
void stop_observing()
{
EM_ASM(clearInterval(globalThis.__libreMidi_timer); globalThis.__libreMidi_timer = undefined;);
}
void start_stream(int port_index)
{
// Isn't life great...
// https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays
const auto& id = m_current_inputs[port_index].id;
EM_ASM(
const port_index = $0;
const id = UTF8ToString($1);
let input = globalThis.__libreMidi_access.inputs.get(id);
function _arrayToHeap(typedArray){
const numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;
const ptr = Module._malloc(numBytes);
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, numBytes);
heapBytes.set(new Uint8Array(typedArray.buffer));
return heapBytes;
}
function _freeArray(heapBytes){
Module._free(heapBytes.byteOffset);
}
input.onmidimessage = (message) => {
let bytes = message.data;
var heapBytes = _arrayToHeap(bytes);
Module._libremidi_devices_input(port_index, message.timeStamp, bytes.length, heapBytes.byteOffset);
_freeArray(heapBytes);
};
, port_index
, id.c_str()
);
}
void stop_stream(int port_index)
{
const auto& id = m_current_inputs[port_index].id;
EM_ASM(const id = UTF8ToString($1);
let input = globalThis.__libreMidi_access.inputs.get(id);
input.onmidimessage = undefined;, id.c_str());
}
std::vector<observer_emscripten*> m_observers;
std::vector<device_information> m_current_inputs;
std::vector<device_information> m_current_outputs;
std::map<int, std::vector<midi_in_emscripten*>> m_opened_inputs;
std::map<std::string, int> m_input_indices;
std::map<std::string, int> m_output_indices;
};
}
}