How to update Midi Device

I have used JUCE about half a year.Now I'm writing a app need to check and open midi device on OSX .I set up a background thread to check updated midi device all the time by using OSX's API . But devices will either never be found, if they are not connected on startup, MIDIGetNumberOfDestinations() and MIDIGetNumberOfSources() coundn't update about the plugged device . If they are connected on startup,app can find out the device,then unplug ,the two functions didn't found out that device had been unpluged. I need to shutdown and restart my app.

any ideas?

juce 3.00 and osx 10.7

Yes - I noticed this last week too. Couldn't find a workaround for it though! It just seems that OSX is caching the devices and doesn't refresh the list until the app restarts. If anyone can find any clues about a way to force the OS to refresh, then please let me know and I'll add it!

It is really a bad news to me. Now i noticed click on the button event can cause the two functions. If use a timer to call the two functions,can it do?

 

Sorry, I don't understand what you mean there.

Perhaps check out http://blog.andrewmadsen.com/post/86220316345/mikmidi-a-new-objective-c-midi-library

Rail

That's only an obj-C wrapper around these same CoreMidi functions, there's no reason to think it would behave any differently.

I haven’t checked out the code… just read the description:

Looking at the code, it looks like he’s responding to MIDINotificationMessageID values kMIDIMsgPropertyChanged, kMIDIMsgObjectRemoved & kMIDIMsgObjectAdded (MIDIServices.h)

Rail

Yeah… in juce_Mac_CoreMIDI.cpp if you add:

    static void globalSystemChangeCallback (const MIDINotification* message, void*)
    {
        // TODO.. Should pass-on this notification..
    
        switch (message->messageID)
        {
            case kMIDIMsgObjectRemoved: break;
            
            
            case kMIDIMsgObjectAdded:   break;
        }
    }

you can handle the adding and removing of a MIDI device while the app is running (this obviously just breaks).

Rail

I think you're misunderstanding - this is not about finding out when a device is added or removed.

The problem is that every time your app asks CoreMidi for its list of devices, it returns the same list. Even after new devices have been added, they're not on the list. If you restart your app, then it gets the correct list, but the OS seems to be caching this somewhere, and I don't know how to force it to re-scan.

Okay, not seeing that on 10.8.5 – using the JUCE Host if I add a USB keyboard and reopen the AudioDeviceSelectorComponent the keyboard is listed without restarting the app… Possibly the issue is with 10.9 and later where the prefs and system files are cached, but the OP is running 10.7

Rail

I saw it happen on 10.10. (It could actually be about the specific driver involved too - very hard to guess what's going on)

Well I checked on 10.10 as well… and found some issues…

Add the following code to juce_Mac_CoreMIDI.cpp globalSystemChangeCallback():

    static void globalSystemChangeCallback (const MIDINotification* message, void*)
    {
        // TODO.. Should pass-on this notification..
    
        const MIDIObjectAddRemoveNotification* addRemoveMsg = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
    
        StringArray tempArray;
    
        switch (message->messageID)
        {
            case kMIDIMsgObjectAdded:   if (addRemoveMsg != nullptr)
                                            {
                                            if (addRemoveMsg->childType == kMIDIObjectType_Source)
                                                {
                                                DBG (newLine + "MIDI Source Device added");
                                                
                                                tempArray = findDevices (false);
                                                
                                                for (int i = 0; i < tempArray.size(); ++i)
                                                    DBG("tempArray[" + String (i) + "] = " + tempArray[i]);
                                                }
                                            
                                            if (addRemoveMsg->childType == kMIDIObjectType_Destination)
                                                {
                                                DBG (newLine + "MIDI Destination Device added");
                                                
                                                tempArray = findDevices (true);
                                                
                                                for (int i = 0; i < tempArray.size(); ++i)
                                                    DBG("tempArray[" + String (i) + "] = " + tempArray[i]);
                                                }
                                            }
            
                                        break;
            
            case kMIDIMsgObjectRemoved: if (addRemoveMsg != nullptr)
                                            {
                                            if (addRemoveMsg->childType == kMIDIObjectType_Source)
                                                {
                                                DBG (newLine + "MIDI Source Device removed");
                                                
                                                tempArray = findDevices (false);
                                                
                                                for (int i = 0; i < tempArray.size(); ++i)
                                                    DBG("tempArray[" + String (i) + "] = " + tempArray[i]);
                                                }
                                            
                                            if (addRemoveMsg->childType == kMIDIObjectType_Destination)
                                                {
                                                DBG (newLine + "MIDI Destination Device removed");
                                                
                                                tempArray = findDevices (true);
                                                
                                                for (int i = 0; i < tempArray.size(); ++i)
                                                    DBG("tempArray[" + String (i) + "] = " + tempArray[i]);
                                                }
                                            }
            
                                        break;
            
            case kMIDIMsgSetupChanged:  if (addRemoveMsg != nullptr)
                                                {
                                                DBG (newLine + "MIDI Setup Changed");
                                                }
            
                                        break;
            
            case kMIDIMsgPropertyChanged: if (addRemoveMsg != nullptr)
                                                {
                                                DBG (newLine + "MIDI Property Changed");
                                                }
            
            default:                    break;
        }
    }

and I found that if there were no devices on the system then the Notification is never registered… so I just moved the code you use to register the Notification in openDevice so it’ll get registered even if there are no devices in the system…

MidiOutput* MidiOutput::openDevice (int index)
{
    MidiOutput* mo = nullptr;
    
    MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient();     // <<---------- Move this here

    if (isPositiveAndBelow (index, (int) MIDIGetNumberOfDestinations()))
    {
        MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index);

        CoreMidiHelpers::ScopedCFString pname;

        if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
        {
        
            MIDIPortRef port;

            if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port)))
            {
                mo = new MidiOutput();
                mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
            }
        }
    }

    return mo;
}

The issue now is that kMIDIMsgObjectAdded isn’t triggered until after kMIDIMsgObjectRemoved is triggered – so if you run the JUCE Host and add a device it won’t trigger kMIDIMsgObjectAdded, but if you then remove and add it again it’ll always trigger kMIDIMsgObjectAdded. kMIDIMsgPropertyChanged is always triggered, so perhaps that could be used instead of kMIDIMsgObjectAdded.

Also wondered if these should trigger a callback to AudioDeviceSelectorComponent to let it know a device has been added or removed and the developer could decide if they want to respond to it to repopulate their device list…???

Cheers,

Rail

We are having strange issues in re-producing this bug. It seems to be a bit random and lately we cannot reproduce it at all. I've created a simple test project which updates the list of MIDI devices when clicking on "update". Can anyone give us a procedure that will trigger the bug with this test project?

That works fine here on 10.8.5 – However, I was hoping we could get a callback to auto-update the devices from globalSystemChangeCallback()

Cheers,

Rail

My program which updates the list of midi devices deponds on a background thread,but not clicking on a button.In thread::run(), i use MIDIGetNumberOfDestinations() and MIDIGetNumberOfSources() to get the number of devices all the time,but the number never changed when i connected or unpluged devices.

1 Like

old thread.. if it's still relevant,  did you make sure that you're polling the number of devices on the main message thread? It seems that Core MIDI open/close/list methods don't like being called from threads other than NSApplicationMain. Making sure that MIDIGetNumberOfSources is called from NSApplicationMain did the trick for me

That's interesting... If that's an official thing, then I should add an assertion when the methods are called from other threads.

It's more or less like this: I had this fully native app, doing the first call to MIDIClientCreate during the initialization on a background thread. Then I was polling the midi inputs on NSApplicationMain, and it wouldn't refresh the list. Moving the first call to MIDICLientCreate to the main thread fixed the misbehavior.

So it seems there are two things: 1. in an application with UI, the first call to MIDIClientCreate should be on NSApplicationMain, otherwise also the subsequent calls to MIDIGetNumberOfSources/MIDIGetNumberOfDestinations on the NSApplicationMain won't refresh the list. 2. the application needs to have a NSApplicationMain

I checked with this console app, that has no NSApplicationMain,  and the sources aren't updated: http://pastebin.com/kS9kX9hP

Checking the JUCE source code, yes, a
jassert (MessageManager::getInstance()->isThisTheMessageThread());
could be added in findDevices.

 

Relevant link

http://stackoverflow.com/questions/19002598/midiclientcreate-notifyproc-not-getting-called

I’m new to juce. The mac I’m using has no physical midi device (not sure if that matters), but when I attempt to build any of the examples in the new juce download I get :
JIT process crashed !
Program used external function
’MIDIGetNumberOfSources’ which could not be resolved!

The default juce_audio_devices path looks OK.

Any idea what I’m missing?

That slightly cryptic crash is actually nothing to do with MIDI, it’s probably because all your JUCE modules have broken paths in the Projucer, so your program’s static initialisers are failing because all the JUCE code is basically missing. Fix your module paths and it should be OK.

We actually hit the same thing here yesterday, and will add some better error messages to prevent you trying to run code if there are modules with broken paths.