Performance bottleneck with BASS_MIDI_StreamEvent(s) under Linux

Started by bree,

bree

Hi Ian,

I've encountered an unusual issue that's causing sluggish playback with Black MIDIs on Linux.

While adding multiplatform support to OmniMIDI (based on the OMv2 codebase), I had several Linux users — including myself — test it with large MIDI files. We all noticed that BASSMIDI on Linux doesn't seem to handle as many events as the Win32 version does.

I ran a series of tests to pinpoint the cause, including timing how long each function takes to return while processing the events buffer. The results consistently point to BASS_MIDI_StreamEvent(s) taking significantly longer to execute on Linux, which leads to playback slowdowns. Interestingly, this happens without any audio dropouts — the streams are simply starved of MIDI data, so it just sounds like the tempo is slowing down.

At first, I suspected it might be due to improper initialization of the libraries, but since the codebase is shared across platforms and both Win32 and Linux are initialized identically, that seems unlikely.

It seems that BASS_MIDI_StreamEvent(s) just performs better on Win32 for some reason. Would it be possible to look into this further?

Thanks in advance!

Ian @ un4seen

That's strange, as the BASS_MIDI_StreamEvent(s) code is the same on all platforms. To confirm that the performance difference is entirely platform-related, are you doing the Linux vs Windows comparisons on the same hardware? Are BASS_MIDI_StreamEvent and BASS_MIDI_StreamEvents equally affected, or one more than the other? Please also confirm whether you're using async events, and whether changing that makes any difference to your performance results.

bree

Quote from: Ian @ un4seenThat's strange, as the BASS_MIDI_StreamEvent(s) code is the same on all platforms. To confirm that the performance difference is entirely platform-related, are you doing the Linux vs Windows comparisons on the same hardware? Are BASS_MIDI_StreamEvent and BASS_MIDI_StreamEvents equally affected, or one more than the other? Please also confirm whether you're using async events, and whether changing that makes any difference to your performance results.
Hi Ian.

Yes, I use the same exact hardware configuration on both OSes (Windows 11 24H2 vs. Fedora Linux 41).

BASS_MIDI_StreamEvents is slightly slower than BASS_MIDI_StreamEvent, but that's probably because it has to internally convert the RAW MIDI data into a BASSMIDI event. Both are slower under Linux, compared to Windows.

I am using the BASS_MIDI_ASYNC flag on both, disabling it makes performance worse on both platforms, with Linux lagging behind Windows.

Ian @ un4seen

OK. Perhaps it's related to synchronization primitives (eg. used for event buffer access) then, as that stuff is platform-specific. I'll look into that.

Is this the code that you're currently using?

    https://github.com/KeppySoftware/OmniMIDIv2/blob/9b2b9e3a37d9d0fba79b9667194a560bafdfc05c/OmniMIDI/src/BASSSynth.cpp

If so, I would suggest removing the BASS_ChannelUpdate calls, at least when using async events. Async event timing granularity isn't affected by update rate, so there's no need for lots of very small updates and perhaps they're delaying the BASS_MIDI_StreamEvent(s) calls. With playback buffering disabled (BASS_ATTRIB_BUFFER=0), the BASS_CONFIG_DEV_PERIOD setting determines the update rate.

If you play a troublesome MIDI file all the way through and then play it again (in the same stream), does it seem better the 2nd time? Please also check the BASS_ATTRIB_MIDI_QUEUE_ASYNC / BASS_ATTRIB_MIDI_QUEUE_BYTE / BASS_ATTRIB_MIDI_QUEUE_TICK values at the end on Windows and Linux.

bree

Quote from: Ian @ un4seenOK. Perhaps it's related to synchronization primitives (eg. used for event buffer access) then, as that stuff is platform-specific. I'll look into that.

Is this the code that you're currently using?

    https://github.com/KeppySoftware/OmniMIDIv2/blob/9b2b9e3a37d9d0fba79b9667194a560bafdfc05c/OmniMIDI/src/BASSSynth.cpp

If so, I would suggest removing the BASS_ChannelUpdate calls, at least when using async events. Async event timing granularity isn't affected by update rate, so there's no need for lots of very small updates and perhaps they're delaying the BASS_MIDI_StreamEvent(s) calls. With playback buffering disabled (BASS_ATTRIB_BUFFER=0), the BASS_CONFIG_DEV_PERIOD setting determines the update rate.

If you play a troublesome MIDI file all the way through and then play it again (in the same stream), does it seem better the 2nd time? Please also check the BASS_ATTRIB_MIDI_QUEUE_ASYNC / BASS_ATTRIB_MIDI_QUEUE_BYTE / BASS_ATTRIB_MIDI_QUEUE_TICK values at the end on Windows and Linux.
Hi,

Yes, that's the code I'm currently using.

The reason I'm creating separate audio threads using BASS_ChannelUpdate is that I implement some multithreading techniques to handle 25–30k active voices in real time. I assign each channel to its own thread, and with the ->ExpMTKeyboardDiv option, I can further subdivide each channel into smaller chunks—for example, setting it to 4 would divide a channel into 4 key chunks, resulting in 16 × 4 = 64 threads.

If I don't run these threads, playback tends to stall. It seems like this is due to the single (possibly built-in?) BASS thread, which updates based on BASS_CONFIG_DEV_PERIOD. That, in turn, leads to audio dropouts.

I'll take a look at the queue values and follow up with another message shortly.

Ian @ un4seen

It looks like those threads are optional? If you don't enable them, is the performance then the same on Windows and Linux?

I notice the threads have different Utils.MicroSleep calls (and implementation) on Windows and Linux, which seem like they could cause timing differences. To check if the problem is related to that, can you try using the same Utils.MicroSleep stuff on Windows as on Linux and see if the problem happens on Windows too then?

Btw, from some initial tests, the synchronization primitives I mentioned appear to be just as fast on Linux as Windows, so it seems unlikely they're causing the problem (but more testing may be needed for edge cases).

bree

Quote from: Ian @ un4seenIt looks like those threads are optional? If you don't enable them, is the performance then the same on Windows and Linux?

I notice the threads have different Utils.MicroSleep calls (and implementation) on Windows and Linux, which seem like they could cause timing differences. To check if the problem is related to that, can you try using the same Utils.MicroSleep stuff on Windows as on Linux and see if the problem happens on Windows too then?

Btw, from some initial tests, the synchronization primitives I mentioned appear to be just as fast on Linux as Windows, so it seems unlikely they're causing the problem (but more testing may be needed for edge cases).
Hi,

Those threads are not optional. Disabling them would actually worsen performance.

I did try disabling them as you requested, and the slowdowns still occurred even without them.

Also, prior to the last few commits, the MicroSleep behavior was identical on both Windows and Linux, yet Linux still experienced worse slowdowns compared to Windows.

Quote from: Ian @ un4seenIf you play a troublesome MIDI file all the way through and then play it again (in the same stream), does it seem better the 2nd time? Please also check the BASS_ATTRIB_MIDI_QUEUE_ASYNC / BASS_ATTRIB_MIDI_QUEUE_BYTE / BASS_ATTRIB_MIDI_QUEUE_TICK values at the end on Windows and Linux.
I tested this, and replaying it a second time does not have improved performance. It still seems to struggle whenever BASS_MIDI_StreamEvent(s) is called too frequently.
What exactly should I be looking for in the queue queries?

Ian @ un4seen

Quote from: breeThose threads are not optional. Disabling them would actually worsen performance.

I did try disabling them as you requested, and the slowdowns still occurred even without them.

Is the performance the same on Windows and Linux then, ie. is there only a difference between them when the threads are enabled? If so, that would seem to suggest that the thread code is where to look for platform-specific differences, such as the MicroSleep stuff. Please try making that stuff the same on Windows and Linux, and see if performance is then the same on both.

Please also confirm that you're using the exact same settings on both platforms, eg. same BASS_SetConfig / BASS_Init / BASS_MIDI_StreamCreate parameters. Some differences there could cause performance differences.

Quote from: breeI tested this, and replaying it a second time does not have improved performance. It still seems to struggle whenever BASS_MIDI_StreamEvent(s) is called too frequently.
What exactly should I be looking for in the queue queries?

What BASS_ATTRIB_MIDI_QUEUE_ASYNC / BASS_ATTRIB_MIDI_QUEUE_BYTE / BASS_ATTRIB_MIDI_QUEUE_TICK values are you seeing (from BASS_ChannelGetAttribute) on each platform? The purpose of checking this is to see if more events are being queued on one platform than the other for some reason (most likely different update rates). When checking these values, please make sure you don't set them yourself first (let them start at the default 0) - BASSMIDI will automatically increase them as needed.