celtera/libremidi

Jack doesn't group virtual MIDI ports under same client

Closed this issue · 16 comments

Hi, I'm trying to create a group of virtual MIDI ports using libremidi. When I pass an identical client name to each port on creation, Jack renames them since instead of using the existing client, it tries to create a new one and then renames it to avoid naming clashes. In the libremidi docs, it sounds like this should not be the expected behavior.

I'd guess the simplest solution would be to amend the midi_in_jack and midi_out_jack classes so that they keep a static list of the Jack clients already created and used (possibly as shared pointers?), and when the name of an existing client is passed, it uses that one instead of creating a new client.

I'm trying to work out the best way to do this, but it's a little tricky. I wanted to check in and see if there was already a solution in place or a plan to implement one. Thank you!

hmmm, I'm thinking that the best way would be to be able to explicitly pass a JACK client to the library, what do you think?

Yeah I think that would work fine. So in that case there would be an extra argument to open_virtual_port to specify the client name? And the only necessary external boilerplate code would be for managing the creation and destruction of the Jack client?

more or less, you'd pass the jack client pointer instead of the name as i don't think the jack API allows to get an existing jack_client_t by name (but I'll check) - as in my experience as soon as you make some remotely advanced software you want to manage your jack client yourself anyways

That would be perfect! I'm trying to create a lot (>50) of virtual ports, so I'm basically just trying to avoid having tons of extra clients scattered around the patchbay but still have everything managed by libremidi.

I'm mocking up a version of jack.hpp that allows passing a client pointer and it seems to be working fine for my purposes. Question: would you want to change the basic midi_in and midi_out classes so a jack_client_t pointer can be passed through to the JACK port constructors only if the JACK backend is used, or would that complicate things too much? I'm imagining scenarios where one might also want to pass an existing pointer to an ALSA client etc., but then things could quickly get out of control. Currently I'm adding a jack_client_t* argument to the midi_in_jack and midi_out_jack constructors that defaults to nullptr if no client is passed, so nothing else needs to change.

Here's what I've done so far if you want to check it out: https://github.com/sadguitarius/libremidi/tree/jack-duplicate-client-fix

Question: would you want to change the basic midi_in and midi_out classes so a jack_client_t pointer can be passed through to the JACK port constructors only if the JACK backend is used, or would that complicate things too much?

It ould not be a very good design I think, instead one can imagine having a opaque "handle" type, e.g. void* that would allow to pass any kind of handle, e.g. alsa seq client or whatever.

I checked your patch: the general idea is ok but it is missing a few things: most importantly right now when the libremidi object gets deleted, it will close the jack_client too which means that any further call will crash. we have to differentiate between the "shared" client and "owned" client case - doing it asap.

hmm and by checking the JACK API doc it's actually harder than I expected: one can only have a single jack "process" callback so for handling midi input from multiple virtual clients I think the fix has to be different...

hmm, but I don't really understand: checking the open_virtual_port, all it does is add a new port to an existing client (and JACK does not support multiple ports having the same name as youcould see).

Right... yeah I definitely need to make that example actually work properly.

So I'm thinking it may somehow make sense to have a shared map of clients that have already been created by libremidi, and any time a new port references a name that already exists as a member of the map, that client is used and its callback is changed. When the reference count of the client drops to zero, it is freed. Something like that. Basically so that the interface would remain unchanged, but no extra clients would be created.

hmm, I am wondering if it wouldn't be better to rework the interface to support multiple ports natively, e.g. by being able to call open_virtual_port multiple times and passing (on write) / being passed (on read) the port index

e.g. you'd do

auto handle1 = midi.open_virtual_port("my port 1"); 
auto handle2 = midi.open_virtual_port("my port 2"); 

and then either do

send_message(handle1, ... midi bytes...); 

and

set_port_callback(handle1, [] (auto message) { callback for port 1;}) 

Yeah something like that I guess? I'm still poking around, I'll post if I come up with anything...

slowly getting there in a cleaner way by allowing to share existing client objects across libremidi object instances:
x

and got it for jack too ^_^

jack