Experimental support for the Windows Runtime MIDI API

Hi everybody,

We’ve just added EXPERIMENTAL support for the Windows Runtime MIDI API to the develop branch, which enables machines with Windows 10 Anniversary Update installed to connect to Bluetooth Low Energy (BLE) MIDI devices.

However, there are some very important caveats:

  1. Once a BLE MIDI device is paired, the WinRT MIDI API will always list it as available, even if the device is powered off.

  2. Sending or receiving sysex messages is very likely to cause a hang.

  3. The WinRT MIDI API may also hang when attempting to connect to a device. We’re currently working around this by running the connection request in a thread and killing it violently after a timeout, but this is clearly a Bad Thing.

These three issues all originate from Windows’ Bluetooth stack, and JUCE’s support for the API will be branded EXPERIMENTAL until a Windows update fixes them. If you have both a Microsoft Account and a Windows 10 Anniversary Update machine then please visit the following URLs (via Edge) to vote for the corresponding, related issues and increase the pressure on Microsoft to get them fixed.

https://aka.ms/gx3nmn
https://aka.ms/T2x4qe

None of these issues are present if you are using the WinRT MIDI API over USB.

Compile-time requirements:

  • Windows 10 Anniversary Update
  • Visual Studio 2015 with version 10.0.14393.0 of the Windows SDK installed (use the Windows add/remove programs settings dialog to modify your VS installation)
  • In the Projucer
    • Select JUCE_USE_WINRT_MIDI in the juce_audio_devices_module
    • Provide the path to the WinRT headers in the Windows SDK in “Header search paths” in a Visual Studio build configuration (probably C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt)

Runtime requirements:

  • Windows 10 Anniversary Update if you want to use BLE, otherwise there’s an automatic fallback to the old MIDI API for older Windows versions; using the new JUCE API will not break compatibility.

Please give this a go and report back any issues!

7 Likes

I’ve just tested this new midi API albeit not with blutooth. The only thing I’ve noticed so far is the list of Active Midi inputs now looks a bit different; previously it looked like

Arturia MINILAB
Scarlett 18i6 USB

now it looks like

4 - Arturia MINILAB [1]
Scarlett 18i6 USB (1)

Any idea where the numbers come from or what they mean?

I’m afraid not; we’re just taking the names straight from the new API. I didn’t notice the addition of a prefix during testing!

The only difference I’ve noticed so far using the new API is that the application fail to reload stored midi settings. The midi inputs are always unchecked upon restarting the application, and default midiout is unselected. Probably its the AudioDeviceManager that doesn’t manage to read back the settings. Storing seems to be alright, xml-file looks ok, but with the strange prefix and suffix as of above.

Please pull the latest develop and try this again - I’ve just made a change that should address this.

Ah no… Now it’s really f*** up. After having to update to the new asio sdk (which of course has nothing to do with this), the Device combo is empty and when trying to select my Focusrite usb driver, I get the Juce message “Error when trying to open audio device! No such device”. Any subseq try to get in contact with my Asio audio device fail.

When inspecting the debug output console i find following disturbing lines repeated twice when starting my app

FFUsb2Asio::FFUsb2Asio()
Exception thrown at 0x00007FF925CF7788 (KernelBase.dll) in Daw2.exe: 0x40080201: WinRT originate error (parameters: 0x0000000080040155, 0x000000000000004A, 0x00000015B07FD650).
+FFUsb2Asio::~FFUsb2Asio()
FFUsb2Asio::stop()
FFUsb2Asio::disposeBuffers()
-FFUsb2Asio::~FFUsb2Asio()

Just to be sure I myself wasn’t to blame for this… misfortune, I replaced the new juce_win32_Midi.cpp (that’s the file right?) with the previous one, and voila, it works again.

To quote Clouseau, Something must be terribly wrong here…:slight_smile:

Ouch. Sorry about that.

Please try again with the latest develop tip.

Thanks. Works now! Apart from the mysterious numbering, that is, but that’s no big concern of mine anyway…

Some more feedback…
I get an access violation whenever I unplug my USB midi input device from my app.

It emanates from

HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo)
{
    const ScopedLock lock (deviceChanges); <<< access violation
    .
    .

I’ll gladly provide you with more info if you can’t provide your own crash by simply unplugging your midi device.

Another regression with the previous fix :unamused:

Everything should be working on the develop branch now!

Thanks, now the violent error is gone…

FYI:

I’m finding the new API a lot more stable when sending sysex data very rapidly from USB-MIDI devices (hundreds of kilobytes of sysex data per second).

Legacy API is subject to very serious packet loss in these situations. However, I’m not sure whether this is a low-level problem or a JUCE Win32 MIDI problem (perhaps too small or to few low-level buffers?)

Considering my packet loss problem, I’m wondering whether the “fallback” solution would re-introduce these problems even when compiling with the new JUCE API. Can you recommend a way to test the new JUCE API specifically with the fallback? Old windows version but same binary? Or is there a smarter way?

My testing has shown that sysex messages shorter then 19 bytes fail to send with the new API, longer messages (which are generally split into multiple parts at a lower level than JUCE) seem to work well.

The fallback is the same as simply not using the new API. When you select the new Windows API in the Projucer then your application will check if the API is available at runtime and, if it is, it will use it, otherwise it will use the older JUCE MIDI code.

An old Windows version but the same binary would do it, or just de-select JUCE_USE_WINRT_MIDI.

Not being able to send short messages is indeed a serious problem. But I actually see a problem also when sending large data. The new API won’t even attempt to transmit a sysex message larger than 256 bytes.

For example:

uint8 testData[512 - 2];
std::fill(testData, testData + sizeof(testData), 0x10);
midiOut->sendMessageNow(MidiMessage::createSysExMessage(testData, sizeof(testData)));

The function WinRTOutputWrapper::sendMessageNow() asserts at this line:

void sendMessageNow (const MidiMessage& message) override
{
    const UINT32 numBytes = message.getRawDataSize();
    HRESULT hr = buffer->put_Length (numBytes);
    if (FAILED (hr))
        jassertfalse; <------ HERE

    memcpy_s (bufferData, numBytes, message.getRawData(), numBytes);

    midiOutPort->SendBuffer (buffer);
}

The HRESULT error code is: E_INVALIDARG (One or more arguments are invalid.)

Reception of large sysex data works flawlessly, so it is really unfortunate that it is instead problematic to send data. Legacy API would send large data without any problems, but wouldn’t receive successfully =)

Aha, I missed the obvious. After some more digging I see that the max buffer size is indeed determined inside the JUCE library code in the MidiRTOutputWrapper constructor.

Would you consider increasing the this from 256 bytes to perhaps 64k?

HRESULT hr = bufferFactory->Create (static_cast<UINT32> (65536), buffer.resetAndGetPointerAddress());

Or perhaps a configurable number depending on the application? We are doing sample dumps to hardware, requiring quite big packets to be efficient.

I’m using 64k buffers now in my modified JUCE class, works fine!

I’d certainly consider it, but there was a motivation for choosing 256 that I’ve since forgotten.

Does everything work as expected for you if you change it?

Sorry, edited message instead of replying. 64k works well, also every other 2^N size below that, run a lot of tests.

OK, I’ll revisit this tomorrow and see if I can work out why I picked 256.

1 Like

https://github.com/WeAreROLI/JUCE/commit/554d055a8f36c1db2c399f031dba3711cd4e1007