libremidi
libremidi copied to clipboard
Blocking API
Hey, really liking libremidi so far. I think it's quite well put together compared to some alternatives I've used in the past.
In general I like the callback based API for realtime messages where I might be sending sequences of notes and such but currently working on a sysex tool for my Casio CZ-1 and I find the callback API to be a bit tedious to work with for this very request-response type of communication that isn't exactly realtime.
It looks something like:
std::deque<libremidi::message> messages;
std::mutex message_mutex;
auto midi_callback = [&](libremidi::message message) {
// fmt::print(stderr, "{::#04x}\n", message);
fmt::print(stderr, "callback\n");
std::unique_lock lock { message_mutex };
messages.push_back(message);
};
auto conf =
libremidi::input_configuration { .on_message = midi_callback, .ignore_sysex = false, .ignore_timing = false };
// ...
auto get = [&]() {
while (true) {
std::unique_lock lock { message_mutex };
if (not messages.empty()) {
auto v = messages.front();
messages.pop_front();
return v;
}
}
};
midi_out.send_message(libremidi::message { 0xf0, 0x44, 0x00, 0x00, 0x70, 0x11, 10, 0x70, 0x31, 0xf7 });
auto req2 = get();
fmt::print(stderr, "{::#04x}\n", req2);
midi_out.send_message(libremidi::message { 0xf0, 0x44, 0x00, 0x00, 0x70, 0x12, 10, 0x70, 0x31, 0xf7 });
auto req3 = get();
fmt::print(stderr, "{::#04x}\n", req3);
Not sure how feasible it is but it would be nice to take advantage of the work libremidi already does to buffer messages and such and keep it high level.
ah, well, this actually was in rtmidi and I explicitely removed it :)
my opinion is that this is much better implemented client-side, just like you're doing right now. The library should strive to offer only the lowest possible level of abstraction that still matches all platform's midi APIs.
what would be actually interesting would be to leverage coroutines for this, to have easy-looking async code: I'm thinking that with boost.cobalt, it could look like this (in particular notice it does not require mutex as this leverages an existing thread-safe task system):
#include "utils.hpp"
#include <libremidi/libremidi.hpp>
#include <boost/asio/detached.hpp>
#include <boost/cobalt.hpp>
namespace asio = boost::asio;
namespace cobalt = boost::cobalt;
// Could go in a libremidi/cobalt.hpp helper
namespace libremidi
{
struct channel
{
cobalt::channel<libremidi::message>& impl;
void operator()(const libremidi::message& message)
{
cobalt::spawn(
impl.get_executor(),
[&impl = impl, message]() -> cobalt::task<void> { co_await impl.write(message); }(),
asio::detached);
}
};
}
cobalt::main co_main(int argc, char** argv)
{
cobalt::channel<libremidi::message> channel_impl{64};
libremidi::channel channel{channel_impl};
libremidi::midi_in midiin{{.on_message = channel}};
midiin.open_port(*libremidi::midi1::in_default_port());
for (;;)
{
auto msg = co_await channel_impl.read();
std::cerr << msg << "\n";
}
co_return 0;
}
see https://github.com/celtera/libremidi/commit/63c4db99fdcf3d4856c40c754d04425415959990
I see your point for sure. I guess my argument is that for most real usecases you're going to need some way to communicate back and forth in the main thread in some potentially blocking way. So if everyone is going to implement the same kind of message queue anyway, I figured maybe it's worth having in libremidi already but I very much understand your perspective.
The example above with async looks very nice so maybe this is the route I should try.
Thanks in any case for responding!
So if everyone is going to implement the same kind of message queue anyway
right, my take on this is that if you're building any kind of multimedia app you likely already have a message queue implementation because it's really the most basic primitive you need in this field. Having one in libremidi would duplicate with a lots of apps already have an implementation of this which increases code size, etc... and would also likely quickly become out-of-date with reference implementations (it's a field which moving fast with constant new developments, e.g. facebook etc. all develop very efficient lock-free queues).
My recommendation if you want a message queue and don't have one is to plug https://github.com/cameron314/readerwriterqueue if you are going to consume from a single thread or https://github.com/cameron314/concurrentqueue if you are going to consume from potentially multiple threads. In particular there's some blocking option in the latter IIRC (BlockingConcurrentQueue) if your app is really only pulling from the main thread and doing nothing else (I really don't recommend having anything blocking if you have any UI or real-time user interaction involved of course =), in this case you really need to structure your program so that it works in a fully async way).
note that the code I posted above is unsafe and needs additional care, I'll fix it soon in the example
(code has been fixed in the example)
closing as I think the example gives enough information, feel free to reopen if additional help is needed!