How to update Midi Device

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.

I got the same error with the default “Audio Application” project. I’ve verified that all the JUCE module paths are correct, and the Xcode target builds successfully (without adding/changing any settings).
The error appears no matter if “create local copy” is set for the modules or not.

Have you tried with the newest version of Projucer built from github? I added some module path checking to it.

I just tried it: Built Projucer from latest Github commit (5049bab). Since it doesn’t contain the JUCECompileEngine.dylib, I added that manually from the latest juce-grapefruit ZIP from the website.

Unfortunately, the same error still appears…I have taken care to not change any settings whatsoever.

+1 running into the exact same issue.

Yeah I can now reproduce the same issue with MIDIGetNumberOfSources. It happens whenever you try to do Projucer live coding with apps that include the audio modules.

I’m on it!

Fixed now, see my post here.

Hi.
I don’t know why, but I might found solution to fix this issue.

Would you try to call MIDIClientCreate before calling MIDIGetNumberOfSources?

(ex) juce_mac_CoreMidi.cpp L172

static StringArray findDevices (const bool forInput)
{
    // It seems that OSX can be a bit picky about the thread that's first used to
    // search for devices. It's safest to use the message thread for calling this.
    jassert (MessageManager::getInstance()->isThisTheMessageThread());

    StringArray s;

    if(getGlobalMidiClient())
    {
        const ItemCount num = forInput ? MIDIGetNumberOfSources()
                                       : MIDIGetNumberOfDestinations();
                                   
        for (ItemCount i = 0; i < num; ++i)
        {
            MIDIEndpointRef dest = forInput ? MIDIGetSource (i)
                                            : MIDIGetDestination (i);
            String name;

            if (dest != 0)
               name = getConnectedEndpointName (dest);

            if (name.isEmpty())
                name = "<error>";
            s.add (name);
        }
    }
    else
    {
        jassertfalse;
        s.add("<error>");
    }

    return s;
}