very, very simple WP with midi CC out

This commit is contained in:
Sebastian
2024-10-09 02:01:23 +02:00
commit 3d6fc151f1
126 changed files with 19539 additions and 0 deletions
@@ -0,0 +1,17 @@
#pragma once
#include <libremidi/config.hpp>
namespace libremidi
{
struct emscripten_input_configuration
{
};
struct emscripten_output_configuration
{
};
struct emscripten_observer_configuration
{
};
}
@@ -0,0 +1,17 @@
#pragma once
#include <libremidi/config.hpp>
#include <string>
namespace libremidi
{
namespace webmidi_helpers
{
struct device_information
{
std::string id;
std::string name;
bool connected{};
};
}
}
@@ -0,0 +1,22 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
extern "C" {
LIBREMIDI_EXPORT
LIBREMIDI_INLINE
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_poll()
{
libremidi::webmidi_helpers::midi_access_emscripten::instance().devices_poll();
}
LIBREMIDI_EXPORT
LIBREMIDI_INLINE
EMSCRIPTEN_KEEPALIVE
void libremidi_devices_input(int port, double timestamp, int len, char* bytes)
{
libremidi::webmidi_helpers::midi_access_emscripten::instance().devices_input(
port, timestamp, len, bytes);
}
}
#endif
@@ -0,0 +1,303 @@
#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);
}
}
void 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;
};
}
}
@@ -0,0 +1,91 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
#include <chrono>
namespace libremidi
{
LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten(
input_configuration&& conf, emscripten_input_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
LIBREMIDI_INLINE midi_in_emscripten::~midi_in_emscripten()
{
// Close a connection if it exists.
midi_in_emscripten::close_port();
}
LIBREMIDI_INLINE libremidi::API midi_in_emscripten::get_current_api() const noexcept
{
return libremidi::API::WEBMIDI;
}
LIBREMIDI_INLINE bool midi_in_emscripten::open_port(int portNumber, std::string_view)
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
if (portNumber < 0 || portNumber >= midi.input_count())
{
error<no_devices_found_error>(
this->configuration, "midi_in_emscripten::open_port: no MIDI output sources found.");
return false;
}
midi.open_input(portNumber, *this);
portNumber_ = portNumber;
return true;
}
LIBREMIDI_INLINE bool
midi_in_emscripten::open_port(const libremidi::input_port& p, std::string_view nm)
{
return open_port(p.port, nm);
}
LIBREMIDI_INLINE bool midi_in_emscripten::open_virtual_port(std::string_view)
{
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
return false;
}
LIBREMIDI_INLINE void midi_in_emscripten::close_port()
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
midi.close_input(portNumber_, *this);
}
LIBREMIDI_INLINE void midi_in_emscripten::set_client_name(std::string_view)
{
warning(configuration, "midi_in_emscripten::set_client_name: unsupported.");
}
LIBREMIDI_INLINE void midi_in_emscripten::set_port_name(std::string_view)
{
warning(configuration, "midi_in_emscripten::set_port_name: unsupported.");
}
LIBREMIDI_INLINE int64_t midi_in_emscripten::absolute_timestamp() const noexcept
{
return system_ns();
}
LIBREMIDI_INLINE void
midi_in_emscripten::on_input(double ts, unsigned char* begin, unsigned char* end)
{
static constexpr timestamp_backend_info timestamp_info{
.has_absolute_timestamps = true,
.absolute_is_monotonic = true,
.has_samples = false,
};
const auto to_ns = [=] { return 1e6 * ts; };
m_processing.on_bytes({begin, end}, m_processing.timestamp<timestamp_info>(to_ns, 0));
}
}
#endif
@@ -0,0 +1,41 @@
#pragma once
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/midi_in.hpp>
#include <libremidi/detail/midi_stream_decoder.hpp>
namespace libremidi
{
class midi_in_emscripten final
: public midi1::in_api
, public error_handler
{
public:
struct
: input_configuration
, emscripten_input_configuration
{
} configuration;
midi_in_emscripten(input_configuration&& conf, emscripten_input_configuration&& apiconf);
~midi_in_emscripten() override;
libremidi::API get_current_api() const noexcept override;
bool open_port(int portNumber, std::string_view);
bool open_port(const input_port& p, std::string_view) override;
bool open_virtual_port(std::string_view) override;
void close_port() override;
void set_client_name(std::string_view clientName) override;
void set_port_name(std::string_view portName) override;
timestamp absolute_timestamp() const noexcept override;
void on_input(double ts, unsigned char* begin, unsigned char* end);
private:
int portNumber_{};
midi1::input_state_machine m_processing{this->configuration};
};
}
@@ -0,0 +1,74 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/midi_out.hpp>
namespace libremidi
{
LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten(
output_configuration&& conf, emscripten_output_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
}
LIBREMIDI_INLINE midi_out_emscripten::~midi_out_emscripten()
{
// Close a connection if it exists.
midi_out_emscripten::close_port();
}
LIBREMIDI_INLINE libremidi::API midi_out_emscripten::get_current_api() const noexcept
{
return libremidi::API::WEBMIDI;
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(unsigned int portNumber, std::string_view)
{
auto& midi = webmidi_helpers::midi_access_emscripten::instance();
if (portNumber >= midi.output_count())
{
error<no_devices_found_error>(
this->configuration, "midi_out_emscripten::open_port: no MIDI output sources found.");
return false;
}
portNumber_ = portNumber;
return true;
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_port(const output_port& p, std::string_view nm)
{
return open_port(p.port, nm);
}
LIBREMIDI_INLINE void midi_out_emscripten::close_port() { }
LIBREMIDI_INLINE void midi_out_emscripten::set_client_name(std::string_view clientName)
{
warning(configuration, "midi_out_emscripten::set_client_name: unsupported.");
}
LIBREMIDI_INLINE void midi_out_emscripten::set_port_name(std::string_view portName)
{
warning(configuration, "midi_out_emscripten::set_port_name: unsupported.");
}
LIBREMIDI_INLINE bool midi_out_emscripten::open_virtual_port(std::string_view)
{
warning(configuration, "midi_in_emscripten::open_virtual_port: unsupported.");
return false;
}
LIBREMIDI_INLINE void midi_out_emscripten::send_message(const unsigned char* message, size_t size)
{
if (portNumber_ < 0)
error<invalid_use_error>(
this->configuration,
"midi_out_emscripten::send_message: trying to send a message without an open "
"port.");
webmidi_helpers::midi_access_emscripten::instance().send_message(
portNumber_, reinterpret_cast<const char*>(message), size);
}
}
#endif
@@ -0,0 +1,37 @@
#pragma once
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/midi_out.hpp>
namespace libremidi
{
class midi_out_emscripten final
: public midi1::out_api
, public error_handler
{
public:
struct
: output_configuration
, emscripten_output_configuration
{
} configuration;
midi_out_emscripten(output_configuration&& conf, emscripten_output_configuration&& apiconf);
~midi_out_emscripten() override;
libremidi::API get_current_api() const noexcept override;
bool open_port(unsigned int portNumber, std::string_view);
bool open_port(const output_port& p, std::string_view) override;
bool open_virtual_port(std::string_view) override;
void close_port() override;
void set_client_name(std::string_view clientName) override;
void set_port_name(std::string_view portName) override;
void send_message(const unsigned char* message, size_t size) override;
private:
int portNumber_{-1};
};
}
@@ -0,0 +1,105 @@
#if defined(__EMSCRIPTEN__)
#include <libremidi/backends/emscripten/midi_access.hpp>
#include <libremidi/backends/emscripten/observer.hpp>
#include <cassert>
namespace libremidi
{
LIBREMIDI_INLINE observer_emscripten::observer_emscripten(
observer_configuration&& conf, emscripten_observer_configuration&& apiconf)
: configuration{std::move(conf), std::move(apiconf)}
{
if (!configuration.has_callbacks())
return;
auto& webmidi = webmidi_helpers::midi_access_emscripten::instance();
webmidi.register_observer(*this);
// Trigger an initial notification
webmidi.load_current_infos();
if (configuration.notify_in_constructor)
update(webmidi.inputs(), webmidi.outputs());
}
LIBREMIDI_INLINE observer_emscripten::~observer_emscripten()
{
if (!configuration.has_callbacks())
return;
webmidi_helpers::midi_access_emscripten::instance().unregister_observer(*this);
}
LIBREMIDI_INLINE
libremidi::API observer_emscripten::get_current_api() const noexcept
{
return libremidi::API::WEBMIDI;
}
template <bool Input>
static auto to_port_info(int index, const webmidi_helpers::device_information& dev)
-> std::conditional_t<Input, input_port, output_port>
{
return {
{.client = 0,
.port = (uint64_t)index,
.manufacturer = "",
.device_name = "",
.port_name = dev.name,
.display_name = dev.name}};
}
LIBREMIDI_INLINE
std::vector<libremidi::input_port> observer_emscripten::get_input_ports() const noexcept
{
std::vector<libremidi::input_port> ret;
auto& webmidi = webmidi_helpers::midi_access_emscripten::instance();
webmidi.load_current_infos();
for (std::size_t i = 0, n = webmidi.input_count(); i < n; i++)
{
ret.push_back(to_port_info<true>(i, webmidi.inputs()[i]));
}
return ret;
}
LIBREMIDI_INLINE
std::vector<libremidi::output_port> observer_emscripten::get_output_ports() const noexcept
{
std::vector<libremidi::output_port> ret;
auto& webmidi = webmidi_helpers::midi_access_emscripten::instance();
webmidi.load_current_infos();
for (std::size_t i = 0, n = webmidi.output_count(); i < n; i++)
{
ret.push_back(to_port_info<false>(i, webmidi.outputs()[i]));
}
return ret;
}
LIBREMIDI_INLINE void observer_emscripten::update(
const std::vector<observer_emscripten::device>& current_inputs,
const std::vector<observer_emscripten::device>& current_outputs)
{
// WebMIDI never remove inputs, it just marks them as disconnected.
// At least in known browsers...
assert(current_inputs.size() >= m_known_inputs.size());
assert(current_outputs.size() >= m_known_outputs.size());
for (std::size_t i = m_known_inputs.size(); i < current_inputs.size(); i++)
{
m_known_inputs.push_back(current_inputs[i]);
configuration.input_added(to_port_info<true>(i, m_known_inputs[i]));
}
for (std::size_t i = m_known_outputs.size(); i < current_outputs.size(); i++)
{
m_known_outputs.push_back(current_outputs[i]);
configuration.output_added(to_port_info<false>(i, m_known_outputs[i]));
}
}
}
#endif
@@ -0,0 +1,37 @@
#pragma once
#include <libremidi/backends/emscripten/config.hpp>
#include <libremidi/backends/emscripten/helpers.hpp>
#include <libremidi/detail/observer.hpp>
#include <vector>
namespace libremidi
{
class observer_emscripten final : public observer_api
{
using device = webmidi_helpers::device_information;
public:
struct
: observer_configuration
, emscripten_observer_configuration
{
} configuration;
explicit observer_emscripten(
observer_configuration&& conf, emscripten_observer_configuration&& apiconf);
~observer_emscripten();
void
update(const std::vector<device>& current_inputs, const std::vector<device>& current_outputs);
libremidi::API get_current_api() const noexcept override;
std::vector<libremidi::input_port> get_input_ports() const noexcept override;
std::vector<libremidi::output_port> get_output_ports() const noexcept override;
private:
std::vector<device> m_known_inputs;
std::vector<device> m_known_outputs;
};
}