jackaudio / jack2

jack2 codebase
GNU General Public License v2.0
2.21k stars 376 forks source link

MIDI 2.0 Universal MIDI Packet format (UMP) #535

Open symdeb opened 4 years ago

symdeb commented 4 years ago

What is the assessment of the impact the MIDI 2.0 packet based protocol will have on Jack audio and MIDI ? Would this need a new ALSA driver/sequencer and a change of Jack2 related APIs ? https://www.midi.org/articles-old/details-about-midi-2-0-midi-ci-profiles-and-property-exchange

falkTX commented 4 years ago

A bit too early to tell, barely anything exists that supports MIDI 2.0 yet. From my understanding, there is no need to change JACK MIDI API, but we might have to do something regarding ALSA-MIDI support. First we have to wait until the ALSA does something about it though

symdeb commented 4 years ago

Link ALSA https://github.com/alsa-project/alsa-lib/issues Link universal packet format https://www.midi.org/articles-old/details-about-midi-2-0-midi-ci-profiles-and-property-exchange) https://www.midi.org/articles-old/midi-2-0-scope https://juce.com/discover/stories/introducing-midi-2-0 Link first MIDI 2.0 Ready product: Roland A-88MKII https://www.midi.org/articles-old/roland-announces

From MIDI.ORG: MIDI 2.0 uses a Universal MIDI Packet (UMP) format for MIDI 1.0 Protocol messages and MIDI 2.0 Protocol messages. There is a Version 2 of the USB Device Class Definition for MIDI Devices in development to support the UMP format. It will require new class drivers. All of the major OS vendors are aware of the progress. I think the ALSA team is also aware.

Note: ALSA team has been made aware of this, see ALSA github repo. https://github.com/alsa-project/alsa-lib/issues/24

bbouchez commented 4 years ago

Hello there, I am a MMA (MIDI Manufacturers Association) member from the MIDI 2.0 workgroup and I am currently working on some prototypes involving MIDI 2.0 on JACK. I can tell that JACK is currently perfectly compatible with MIDI 2.0 UMP packets. I have performed various tests on Windows and Linux platforms and the JACK servers on these two platforms are passing UMP packets perfectly.

symdeb commented 4 years ago

Is that between client created ports or with physical USB devices using the raw or sequencer setting in jack ? How would that work with Windows Win32 MIDI API that lies underneath Jack in Windows since the win32 API only supports a DWORD for MIDI events.

bbouchez commented 4 years ago

For now, there are no "official" physical devices yet, as the standard group is still discussing about USB implementation (there is nothing officially released about USB MIDI 2.0). On my side, I am working on Ethernet based devices, which do not need system dependent drivers. One of the tools I have created is a RTP-UMP daemon, which allows to exchange UMP packets between different physical machines. About Windows API : the problem is that there is not yet any official API (MME on Windows or CoreMIDI on Mac) which supports UMP packets for now. So you should not expect any existing Windows software to be compatible with UMP. For now, the only way to get these messages on a Windows machine is to use JACK to circumvent the MME API limitations.

symdeb commented 4 years ago

Thanks. that explains it Out of curiosity just verified UMP on Jack (Linux) with client registered ports. The data messages of 4 (and 8) bytes can indeed be exchanged between the ports. However when the packet reaches the ALSA level the first byte gets removed so only a 3 byte legacy MIDI message remains and this does reach the ALSA hardware device (or software clients) correctly. Do however get an error stack smashing detected : terminated Aborted (core dumped) when disconnecting ports & deactivating Jack. That does not occur with messages of 3 bytes.

bbouchez commented 4 years ago

Sounds logic. ALSA (like Win MME or Core MIDI) knows nothing about MIDI 2.0. That's why I am using RTP-UMP (based on RTP-MIDI) hardware interfaces. From software point of view, JACK's clients must be programmed specifically to recognize UMP. I plan to work on some Open Source synths later (especially for the Zynthian target) By the way, UMP is not only 4 and 8 bytes, there are also 16 bytes messages (but they pass through JACK like the others). From what I can see, JACK works like if these messages are SYSEX but it does not check that they start with 0xF0 (which is pretty cool as it makes JACK compatible de facto with UMP °-)

cbix commented 3 years ago

https://github.com/atsushieno/cmidi2

might be useful for client implementations.

jcelerier commented 1 year ago

things are starting to move, ALSA has alsa_ump support now: https://github.com/alsa-project/alsa-lib/blob/master/include/ump.h ; CoreMIDI has it since macOS 11.0 / iOS 14 (https://developer.apple.com/documentation/coremidi/midi_services/incorporating_midi_2_into_your_apps/) and there's WIP support on windows: https://devblogs.microsoft.com/windows-music-dev/the-new-windows-midi-services-spring-2023-update/

I'm currently adding support to https://github.com/jcelerier/libremidi

symdeb commented 2 months ago

After further testing jack_midi_event_write does allow to send to UMP data , however jack_midi_get_event_count and jack_midi_event_get attempt to read the data as legacy as such only sending UMP seems to work with jack, not receive..

bbouchez commented 2 months ago

I do not use jack_midi_event_write to send UMP, I use jack_midi_event_reserve. JACK documentation says that jack_midi_event_write is a wrapper around jack_midi_event_reserve, but it may perform some extra processing which blocks messages which do not comply to MIDI 1.0 (have to check in JACK source code). With jack_midi_event_reserve, I am able to send any UMP message, as you can see on the screenshot

Capture

Here is the test code I use to send UMP blocks.

int jack_process (jack_nframes_t nframes, void arg) { void out_port_buf = jack_port_get_buffer(OutputPort, nframes); jack_midi_data_t* Buffer;

jack_midi_clear_buffer(out_port_buf);    // Recommended to call this at the beginning of process cycle

if (DoTest1)
{    // Test MIDI 1.0
    Buffer=jack_midi_event_reserve (out_port_buf, 0, 3);
    if (Buffer!=0)
    {
        Buffer[0]=0xB0;
        Buffer[1]=0x40;
        Buffer[2]=0x7F;
    }
    DoTest1=false;
}

// Test sending 32 bits UMP message
if (DoTest2)
{
    Buffer=jack_midi_event_reserve (out_port_buf, 0, 4);
    if (Buffer!=0)
    {
        Buffer[0]=0x20;     // MT=2, group=0
        Buffer[1]=0xB0;
        Buffer[2]=0x40;
        Buffer[3]=0x7F;
    }
    DoTest2=false;
}

// Test sending 64 bits UMP message
if (DoTest3)
{
    Buffer=jack_midi_event_reserve (out_port_buf, 0, 8);
    if (Buffer!=0)
    {
        Buffer[0]=0x40;     // MT=4, group=0
        Buffer[1]=0xB0;
        Buffer[2]=0x40;
        Buffer[3]=0x00;
        Buffer[4]=0x12;     // 32 bits CC
        Buffer[5]=0x34;
        Buffer[6]=0x56;
        Buffer[7]=0x78;
    }
    DoTest3=false;
}

// Test sending 128 bits UMP message (Device Identity Notification)
if (DoTest4)
{
    Buffer=jack_midi_event_reserve (out_port_buf, 0, 16);
    if (Buffer!=0)
    {
        Buffer[0]=0xF0;     // MT=F
        Buffer[1]=0x02;     // Status=2
        Buffer[2]=0x00;
        Buffer[3]=0x00;

        Buffer[4]=0x00;
        Buffer[5]=0x00;     // SYSEX ID 1
        Buffer[6]=0x20;     // SYSEX ID 2
        Buffer[7]=0x7C;     // SYSEX ID 3  (thank you KissBox BV!!! °-) )

        Buffer[8]=0x01;     // Device family LSB
        Buffer[9]=0x00;     // Device family MSB
        Buffer[10]=0x01;    // Device model LSB
        Buffer[11]=0x00;    // Device model MSB

        Buffer[12]=0x01;    // SW revision = 1.0.0.0
        Buffer[13]=0x00;
        Buffer[14]=0x00;
        Buffer[15]=0x00;
    }
    DoTest4=false;
}

TotalFrames+=nframes;

return 0;

} // jack_process

symdeb commented 2 months ago

Thanks a lot, this helps. I have added a Feature Request for adding UMP support in the jack_midi_event_write call. Guess this needs to be done both in jack2 as well as in the pipewire jack emulator (?).

bbouchez commented 2 months ago

I have released the complete source code of my two UMP/JACK demo applications. I have provided the links in a discussion I created : https://github.com/jackaudio/jack2/discussions/985

cbix commented 2 months ago

I really like the ALSA implementation allowing clients to explicitly specify legacy or UMP and taking care of transparent translation between the two according to the MIDI 2.0 spec. Wouldn't it make sense to extend the JACK API in a similar way, so legacy and MIDI 2.0 applications can interact transparently? IIUC adding a port type is not a breaking change to the ABI, or am I missing anything?

symdeb commented 2 months ago

ALSA sequencer API supports all the UMP features, its a great API. ALSA does a great job for all translation handling. Since jack seems to use raw MIDI, it is quite difficult to determine a port supports UMP or not and to obtain the port capabilities. So far it seems there isn't a method to identify this from the current ALSA raw port API. The other challenge is the possible complexity of non-static function blocks when port capabilities change on the fly. Not sure how far the raw API changes will impact DAWs and other future UMP apps but could likely become quite a challenge for client applications. Ofcourse, It is possible to use the ALSA sequencer without the scheduling feature

symdeb commented 2 months ago

jack_midi_event_write works fine. The issues is reading UMP from ports. jack_midi_get_event_count and jack_midi_event_get . I used the same calls in in that demo app, but somehow didn't work. Those may work when its between two software jack ports because there isn't ALSA in between, though my use case is reading data from an actual MIDI UMP hardware device. Need check further..~~~ if someone has a Protozoa and verify ?

cbix commented 2 months ago

So far it seems there isn't a method to identify this from the current ALSA raw port API.

There is, check the kernel docs I linked above:

When the MIDI 2.0 device is probed, the kernel creates a rawmidi device for each UMP Endpoint of the device. Its device name is /dev/snd/umpC*D* and different from the standard rawmidi device name /dev/snd/midiC*D* for MIDI 1.0, in order to avoid confusing the legacy applications accessing mistakenly to UMP devices.

With the important note:

Unlike the MIDI 1.0 byte stream, UMP is a 32bit packet, and the size for reading or writing the device is also aligned to 32bit (which is 4 bytes).

The 32-bit words in the UMP packet payload are always in CPU native endianness

While it may certainly work to send UMP over an 8-bit MIDI port in JACK if both ends understand UMP, in real-world scenarios it's just not as practical as having a separate 32-bit UMP port type and make JACK handle translation between the two, e.g. when a UMP port is connected to a legacy MIDI port.

The other challenge is the possible complexity of non-static function blocks when port capabilities change on the fly

I agree, sounds to me like another reason to make JACK "aware" of UMP ;)

if someone has a Protozoa and verify

Off-topic but in the past I've used a RasPi Zero 2 in USB gadget mode for this, based on the kernel docs, see https://gist.github.com/cbix/97a341c2857fd4f55d0cd19ccf6c354b