libremidi icon indicating copy to clipboard operation
libremidi copied to clipboard

MIDI polling API

Open jcelerier opened this issue 5 years ago • 8 comments

@Be-ing I experimented here with a synchronous poll interface which would allow you to control how the library is threaded, would that be good for your use case ?

For now I only added to the new raw alsa midi backend (as it's new and thus no one is harmed :-)) but if that's good I'll look into porting the other backends to the same API.

jcelerier avatar Jan 18 '21 11:01 jcelerier

Hm, I just did an overview of the other backends and I'm not sure it's worth it... e.g. for instance both JACK and MME are mandatorily asynchronous and manage their own thread, so poll() would just be doing sleep() and repatriate the callback in the user thread...

jcelerier avatar Jan 18 '21 12:01 jcelerier

@daschuer what do you think about this?

Be-ing avatar Jan 18 '21 21:01 Be-ing

Hi @jcelerier,

I have dig a bit into this source and I think the new polling interface looks reasonable. Does it support time stamps? If not that should be added. This allows to use a normal threads and long intervals and still have all received messages without a jitter.

I think the implementation using the ALSA RawMidi Interface is not really intended for a (slow) user driven thread because of the missing time stamp.

I can think of polling midi from the audio callback to have midi and audio in sync and gain minimum latency. Jack2 does this for instance. It works great for midi keyboard because of the lowest possible latency. However it comes at the costs of a jitter of up to one audio buffer size. For continuous controls like jog-wheels it is hard to recreate a continues movements.

It also requires that the midi mapping is processed in the audio thread or at least with such high priority that it is completed during one audio buffer size.

A locking implementation with a worker thread sleeping at the poll call seems to be an alternative use case. It has the chance to create time stamps. However this suffers the CPU concurrency issue, because it may lock the CPU in a bad moment, or collecting a significant jitter by being locked.

The ALSA Sequencer interface should works better in this regards, it is designed to deliver the midi messages in time, implemented in the Kernel space. I am not sure how exact the time stamp really is if we consider that today all midi communication is tunneled via USB. But at least it removes one source of jitter noise.

Do you know if this has also bigger latency? Jack uses this interface in its own polling thread which introduces extra latency.

Do the other APIs have similar features?

daschuer avatar Jan 27 '21 21:01 daschuer

Personally not sure if a polling api for messages makes sense, but, for devices, and their connection status I could see that.

cpyarger avatar Feb 14 '21 20:02 cpyarger

Can you explain your considerations a bit more?

For my understanding we have a system interrupt that is called whenever new midi messages are available. These messages are collected in a buffer. We can now either have a user thread sleeping that is signaled in that case or we have a cycling thread that runs anyway that can look if there if a new message. Both methods have there use cases.

daschuer avatar Feb 14 '21 21:02 daschuer

With the obs-midi project, I haven't had any issues with real-time messages, as fast as they come in they get handled with the current callback method. including multiple control change faders being used simultaneously with each value change being acted on.

cpyarger avatar Feb 14 '21 21:02 cpyarger

The issue is, that you can't be sure how old the messages are, if you consider that the sleeping thread does not have real time priority. Normally the midi data affect the audio thread. You can control the audio only between the cycles of the audio buffer. With such a sleeping thread, you effectively only copy the midi data from one buffer to an other, and than you have the polling from the audio thread anyway.

daschuer avatar Feb 15 '21 00:02 daschuer

I agree that we must find a way to timestamp data properly. Note that my implementation for now is mostly prototypal in order to validate the API before committing it - I don't think there would be any issues with adding the timestamp here and in other implementations (I added it to the rawmidi implementation because it does not have users yet so things can be broken safely)

jcelerier avatar Feb 15 '21 09:02 jcelerier

this is now done in a much much better way in branch v4 and should have your considerations fixed @daschuer :

  • for ALSA (sequencer and raw) it's now possible to control polling explicitly as I added a way to get back the file descriptors used by ALSA: https://github.com/jcelerier/libremidi/blob/v4/tests/poll_share.cpp - so you can put the processing in whichever thread is desired, with RT scheduling if wanted, etc

  • I added timestamp support to the raw alsa backend and overhauled the timestamp handling across all the library - now there's a way to choose:

    • whether timestamping is desired
    • where do the timestamps come from and how they are computed: either from the API in relative mode (like RtMidi but it wasn't implemented yet for all backends supported in libremidi - now yes), from the API in absolute mode (basically the timestamps given back by each API directly), or if preferred and if it makes a difference, from the system monotonic clock
    • also all the timestamps have been normalized to nanoseconds

jcelerier avatar Jul 24 '23 16:07 jcelerier