Hello everyone, I was working on a Raspberry Pi setup, and I noticed something that caught my attention regarding the OutputImplNative (juce_audio_devices/native/juce_Midi_linux.cpp).
In OutputImplNative::send() there is a check for choosing between sending UMP or bytestream based on the presence of an ALSA symbol:
if (snd_seq_ump_event_output_direct != nullptr)
{
for (const auto& v : makeRange (b, e))
sendUmp (v);
}
else
{
for (const auto& v : makeRange (b, e))
sendBytestream (v);
}
But, this only checks whether the function is available in alsa-lib, not whether the underlying ALSA sequencer/kernel actually supports ump.
When the symbol is present (alsa-lib >= 1.2.10, if I’m not wrong), but the kernel does not have UMP modules enabled (e.g., missing or not loaded) the code will take the sendUmp() path, but ALSA will fail sending the MIDI at runtime.
The same check is used also in OutputImplNative::sendUmp(), and fortunately there is also a jassert that helped me understand my problem.
void sendUmp (ump::View v)
{
if (snd_seq_ump_event_output_direct == nullptr)
return;
snd_seq_ump_event_t event{};
// ... other code
[[maybe_unused]] const auto code = snd_seq_ump_event_output_direct (port->getClient()->getSequencer(), &event);
jassert (code >= 0);
}
In my case, code was -EBADFD (bad descriptor), which made me wonder whether the UMP modules were missing from my RPi setup.
After doing a few checks and tests, I realized that they were actually not present, and I must say I was surprised that UMP isn’t supported by default on the kernel of Raspberry Pi OS (I used the latest stable version via the RPi Imager).
So my solution was simply to rebuild the kernel enabling UMP support, and of course everything worked after.
However, in my view, it would be better to have a more robust check performed earlier, when choosing between sendUmp and sendBytestream, instead of relying on a jassert in debug.
There may be cases where someone might not want to (or cannot) rebuild the kernel or enable the UMP modules. In those cases, the current code still chooses UMP and fails.
I think that it would be better to detect UMP capability once during construction of OutputImplNative, store a flag and use that flag in OutputImplNative::send() to choose between sendUmp() or sendBytestream().
Something like:
class OutputImplNative : public ump::Output::Impl::Native
{
public:
OutputImplNative (ump::DisconnectionListener& l, std::shared_ptr<Port> portIn)
: listener (l), port (std::move (portIn))
{
snd_midi_event_t* ptr = nullptr;
[[maybe_unused]] const auto code = snd_midi_event_new (maxEventSize, &ptr);
jassert (code == 0);
midiParser.reset (ptr);
// Check regarding isUmpSupported
if (snd_seq_ump_event_output_direct != nullptr)
{
if (auto* seq = port->getClient()->getSequencer())
isUmpSupported = seq->midi_version > 0;
}
port->addDisconnectionListener (listener);
}
~OutputImplNative() override
{
port->removeDisconnectionListener (listener);
}
bool send (ump::Iterator b, ump::Iterator e) override
{
if (isUmpSupported)
{
for (const auto& v : makeRange (b, e))
sendUmp (v);
}
else
{
for (const auto& v : makeRange (b, e))
sendBytestream (v);
}
return true;
}
// ... other code unchanged ...
private:
// New flag
bool isUmpSupported = false;
ump::DisconnectionListener& listener;
std::shared_ptr<Port> port;
// ... other code unchanged ...
};
I don’t think I’ve seen anyone else mention this on the forum (my apologies if I’ve missed it), so I just wanted to share this, in case it’s something worth looking into also for others who use JUCE with Linux like me
