Behavior of MidiInput::getDevices() has changed?

It seems the behavior of MidiInput::getDevices() has changed on Mac OS X. Under 10.5.8 I was able to plug in an USB keyboard while the app was already running, redo a scan with MidiInput::getDevices() and get an updated list. Since I moved to 10.6.8, this is no longer the case. The plugged device does not appear in the list, no matter how often I do a rescan. I need to shutdown and restart my app.

I should note that I also moved to the new “modules” branch, so I wonder if this change is due to the OS update or the Juce update?

Any idea?

Well, if you have a look inside that function, you can see that every time you call it, it’ll ask the OS for its device list.

Then the OS behavior has changed. It might require an extra call (clear some cache?) to ensure the entire list is queried.

Needing to restart the app only because one forgot to plug in the USB keyboard is really annoying.

There’s a change notification callback for OSX for MIDI devices, although this is not available on Windows. I have not checked if the USB action is noticed at all yet.

Sorry for bringing this up again, but the issue is still there and it is extremely annoying, because one is forced to shutdown and restart an application only to rescan the available MIDI ports. Here’s what I’ve found out so far:

Enumerating endpoints with MIDIGetNumberOfDestinations() and accessing them with MIDIGetDestination (i) (and their respective counterparts for sources) does not work anymore on OS X 10.6 and 10.7. The issue being that USB devices will either never be listed, if they are not connected on startup, or listed forever regardless whether they are disconnected later. No matter how many times MidiOutput::getDevices() is repeated. :cry:

I found another method of enumerating endpoints by devices (see source code). It works as expected, although it does not list virtual endpoints and the resulting StringArray does not correspond to the same indexing that other Juce methods would need to operate.

IMHO, it is well worth looking into this in a collaborative effort, because it pretty much degrades MIDI connectivity for all Juce apps on the Mac. A few ideas to try out:

  1. Is there something we could reset/flush in CoreMIDI before requesting the device list?
  2. Would it be possible to use the code below in CoreMidiHelpers, add a hack for virtual endpoints, and change the other MidiInput & MidiOutput methods to use that indexing instead?
  3. Would registering for a CoreMIDI change notification callback perhaps do the trick?

I will try 3 right now and let you know.

Any insights and contributions to this are highly appreciated! Thanks.

// This works great, but indexing is incompatible with existing Juce code

void getDeviceEnpointNames (bool inputs, StringArray& strings)

ItemCount deviceCount = MIDIGetNumberOfDevices ();
for (ItemCount i = 0 ; i < deviceCount ; ++i) 
    MIDIDeviceRef device = MIDIGetDevice (i);
    SInt32 offline = 0;
    MIDIObjectGetIntegerProperty (device, kMIDIPropertyOffline, &offline);
    ItemCount entityCount = MIDIDeviceGetNumberOfEntities (device);

    if (!offline) 
        for (ItemCount j = 0 ; j < entityCount ; ++j) 
            MIDIEntityRef entity = MIDIDeviceGetEntity (device, j);
            String name;
            if (inputs)
                ItemCount count = MIDIEntityGetNumberOfSources (entity);
                for (ItemCount k = 0 ; k < count ; ++k) 
                    MIDIEndpointRef endpoint = MIDIEntityGetSource (entity, k);
                    name = getConnectedEndpointName (endpoint);
                ItemCount count = MIDIEntityGetNumberOfDestinations (entity);
                for (ItemCount k = 0 ; k < count ; ++k) 
                    MIDIEndpointRef endpoint = MIDIEntityGetDestination (entity, k);
                    name = getConnectedEndpointName (endpoint);                
            if (!name.isEmpty())
                strings.add (name);          

} [/code]

AAAHH! :smiley:

It seems the thing that changed from 10.5 to 10.6 is that querying devices is required to happen on the message thread. I tested in a minimal Juce app, triggered getDevices() by a button press, and didn’t see the issue. In my app, this is done by a server thread (worker).

Jules, would it be possible to add an assertion or method comment that indicates this requirement?

Sorry for the noise.

Ah! Interesting. Thanks, I’ll get an assertion in there!

While changing my app to comply with this, the main constraint seems to be that the global MIDI client needs to be created on the message thread. Therefore the assertion should best be placed where this is done: in CoreMidiHelpers::getGlobalMidiClient(). Or in every method that calls it.

Once this was ensured, my app did no longer have issues querying the devices anymore, even when this was done from a secondary thread.

Also, if the client is not created on the message thread, it does not receive MIDINotification messages when hardware is connected and disconnected (NotificationCallbackProc). Although this feature is not implemented in the Juce library (yet), this observation is still an indicator that the above assumption is correct. I would speculate that Apple streamlined their code to use the notification mechanism to flush an internal cache.

That said, I also added this in CoreMidiHelpers namepspace:

void NotificationCallback (const MIDINotification *message, void *refCon) { DBG ("NotificationCallback"); }

and used this for the client creation:

The debug output proved that the callback was working now. It is probably not bad to add an empty stub to the Juce code, so users can more easily debug their midi system.

Ok, thanks for the info!

We experienced something similar once, and got to the conclusion that what was needed is to call the message pump. If nobody never calls the message loop, MIDI devices lists are not updated.

I’m running into the same problem. The MIDI devices are updates in a background worker thread in our application.

Any suggestions about a solution that would ensure that updated MIDI destinations and sources are detected without moving that logic into another thread?

On a related note, is there a way to uniquely identify MIDI devices besides the name? What happens when there’s duplicates (for example, two “Generic MIDI Input” devices) ?

As far as I understood, it is the responsibility of Juce to ensure a unique name. Take a look at CoreMidihelpers namespace. There are a few methods aimed at determining sort of “the most likely name” for a midi endpoint (Mac).

So far I didn’t run into name conflicts yet.

That may be the case but I don’t see any code in the JUCE Win32 Midi to handle duplicates. And they can certainly come up, look at this article:

I think there’s a deeper problem. If a musician unplugs a MIDI device from the other end of the cable from the compyter, and it doesn’t have active sensing that has been enabled, there’s no way for the midi driver to know. Many people turn off active sensing, even if it’s supported, because the MIDI data rate is already quite slow. As such, neither Apple nor Microsoft can ensure that the a device has been unplugged at the other end of the cable. The MIDI protocol itself would have to change. Hence, at best there can only be a partial solution, at least as far as I can think. Maybe I haven’t thought of something.

This is probably true. MIDI seems quite outdated and shows its age in many ways.

I think a more USB oriented approach for detecting insertions and removals would be best, but JUCE is severely lacking in this area. Perhaps this would be a good field of development for third party modules?

Could be, for good reason. As the interface itself can’t guarantee that a device has been plugged or unplugged, both Apple and Microsoft have a viable reason not to provide that functionality in the software. They could require notifications from MIDI clients in software-only devices, but there’s no such requirement currently, as far as I know, and the nortification logically would derive from the application, rather than the OS. The OS could provide notifications, but again, the application istelf may connect or disconnect a MIDI interface without notifying the operating system, so again such notifications would only be a partial solution.

That is to say, an application is not required to notify the operating system that it has started or stopped using a particular MIDI device or channel after application startup. This is a shortfall in an ancient protocol predating plug and play. What about a plugin for audio applications which provides a better communications protocol for musical note data independent of the MIDI interface, making the musical note data available between applications on a separate layer? That would enable, for adding and removing of multiple apps which can change note and timing data structures independent of MIDI. Then one could, for example, start an app which changes a melody dependent upon colors from a videocam, and insert it into a data stream between a music generator and music player just by starting the app. Is there a way to do that with the existing MIDI protocol? I think not, I think one has to disconnect the note data sender and receiver, then reconnect them with the new app in the middle, and it’s not possible to do so without interrupting the data stream. So yes, I do perceive a real opportunity for third-party development there, and the result could enable many different apps to share and change musical data in new ways.

Maybe this belongs in a separate thread elsewhere, I’m not sure where to post, but with regards to getDevcies(), there may be limiations in JUCE, but there are protocol limitations which are insurmountable in the first place, so beyond not requiring a restart of an app, I don’t believe it makes sense for Giles to put effort into ensuring that device notifications are passed up to JUCE apps, brecause the devices on the other end may not be providing the notifications in the first place, that’s all )

For MIDI that might be true, but there’s definitely value in having USB support in JUCE, or in a 3rd party module. Same goes for HID devices.

As USB interface does provide notifications, is there a way to create a virtual USB device, so it could provide shared memory between multiple apps?