Cannot open multiple MidiOuts

When I attempt to open multiple MidiOuts, only the first MidiOutput::openDevice() works. The subsequent ones return nullptr. I think at one stage this was happening intermittently. So it is possible you may not be able to reproduce the problem. But now it happens every time for me. In the following code, it makes no difference if I change the order in which the three named MIDI devices are opened. Every time, it is only the first one opened that works.

    const auto midiOutput1 = MidiOutput::openDevice("01. Internal MIDI");
    if (midiOutput1 == nullptr) {
      // Never happens with the first MidiOut opened.
      DBG("Failed to open midiOutput1");
    }
    const auto midiOutput2 = MidiOutput::openDevice("02. Internal MIDI"); 
    if (midiOutput2 == nullptr) {
      // Always happens with the second MidiOut opened.
      DBG("Failed to open midiOutput2");
    }
    const auto midiOutput3 = MidiOutput::openDevice("03. Internal MIDI"); 
    if (midiOutput3 == nullptr) {
      // Always happens with the third MidiOut opened.
      DBG("Failed to open midiOutput3");
    }

In my previous post, when I mentioned that at one stage the problem may have been happening intermittently, that was when I had tried opening each MidiOutput in a separate thread. I have recreated that in the following code:

#pragma once
#include <JuceHeader.h>
using namespace juce;

class MidiOutputOpener : public Thread {
public:
  explicit MidiOutputOpener(const String& deviceName)
    : Thread(deviceName) {
  }

  String getDeviceName() const {
    return getThreadName();
  }

  void run() override {
    const auto deviceName = getDeviceName();
    if (const auto midiOutput = MidiOutput::openDevice(deviceName);
      midiOutput != nullptr) {
      DBG("Opened " + deviceName);
    } else {
      DBG("Failed to open " + deviceName);
    }
  }
};

class MidiOutTester {
public:
  OwnedArray<MidiOutputOpener> midiOutputOpeners;

  void startMidiOutTester() {
    midiOutputOpeners.add(new MidiOutputOpener("01. Internal MIDI"));
    midiOutputOpeners.add(new MidiOutputOpener("02. Internal MIDI"));
    midiOutputOpeners.add(new MidiOutputOpener("03. Internal MIDI"));
    DBG("=====================================");
    for (const auto midiOutputOpener : midiOutputOpeners) {
      midiOutputOpener->startThread();
    }
  }

   void stopMidiOutTester() {
    for (const auto midiOutputOpener : midiOutputOpeners) {
      if (midiOutputOpener->isThreadRunning()) {
        midiOutputOpener->stopThread(10);
      }
    }
    midiOutputOpeners.clear();
   }
};

And here are the results of a few attempts to open three MidiOuts in separate threads with the above code. The behaviour has indeed been variable. Sometimes two out of three succeed in opening, sometimes only one:

=====================================
Opened 03. Internal MIDI
Failed to open 01. Internal MIDI
Failed to open 02. Internal MIDI
=====================================
Opened 02. Internal MIDI
Failed to open 03. Internal MIDI
Failed to open 01. Internal MIDI
=====================================
Opened 01. Internal MIDI
Failed to open 03. Internal MIDI
Opened 02. Internal MIDI
=====================================
Opened 02. Internal MIDI
Failed to open 01. Internal MIDI
Opened 03. Internal MIDI

Have you tried to add small sleep in between the opening tries? I‘d start with five seconds to be sure and then narrow it down if it is indeed working this way.

Thanks @Rincewind! That worked. I got the sleep down to 0. sleep(0) is equivalent to yield(), which is defined as

Yields the current thread’s CPU time-slot and allows a new thread to run. If there are no other threads of equal or higher priority currently running then this will return immediately and the current thread will continue to run.

So I replaced sleep(0) with yield(). That works too. Here’s a working version of the code:

class MidiOutputOpener : public Thread {
public:
  explicit MidiOutputOpener()
    : Thread("MidiOutputOpener") {
  }

  void run() override {
    DBG("=====================================");
    openMidiOutput("01. Internal MIDI");
    yield();
    openMidiOutput("02. Internal MIDI");
    yield();
    openMidiOutput("03. Internal MIDI");
  }

  void openMidiOutput(const String& deviceName) const {
    if (const auto midiOutput = MidiOutput::openDevice(deviceName);
      midiOutput != nullptr) {
      DBG("Opened " + deviceName);
    } else {
      DBG("Failed to open " + deviceName);
    }
  }
};

class MidiOutTester {
public:
  MidiOutputOpener midiOutputOpener;
  
  void startMidiOutTester() {
    midiOutputOpener.startThread();
  }

   void stopMidiOutTester() {
    if (midiOutputOpener.isThreadRunning()) {
      midiOutputOpener.stopThread(10);
    }
   }
};

And here is the consistent result:

=====================================
Opened 01. Internal MIDI
Opened 02. Internal MIDI
Opened 03. Internal MIDI

It would be interesting, though not essential, to know more about why the yield() is necessary. For a start, it shows that MidiOutput::openDevice must be running one or more new threads behind the scenes.

It is very much possible, that every OS deals with that differently.

I wouldn’t necessarily draw this conclusion. If that is the fact and without the yield you run into a try lock from the OS, you would be better of by sleeping actually a second since this kind of behaviour is not deterministic and just because it worked the first five tries, doesn’t mean it always works.

The easiest and most robust solution would be to open sequentially from the message thread, if that is not stopping for too long.

@Rincewind and @daniel, those are both most informative comments, thanks. It’s a good point that I’ve not proven that my fix will work always and everywhere. And I did not know that doing this in the GUI thread would be more robust. Those yields should not tie up the GUI thread too long. So I shall do that.

And if yields on the message thread proven not to be robust enough, I can go with longer sleeps on another thread. There are multiple good options.

I think you shouldn’t yield on the message thread, since there is always stuff to do. Just use a juce::Timer as I suggested to delay it 1ms in case you want to go down that route.

Ah yes, that sounds optimal. Thanks @Rincewind.

For efficiency, I would like each MidiOutput to have its own thread from which to send MIDI messages. But I have only been able to access a MidiOutput's member functions on the thread on which the MidiOutput was instantiated/opened. So I need either

  • a way to access the member functions from a different thread from where the MidiOutput was opened
  • or a way to open each MidiOutput on its own thread.

The latter approach is shown in the following code. Unfortunately it only succeeds in opening one MidiOutput, regardless of how many are requested. First, here’s the MidiOutBus class, which attempts to open a MidiOutput on a thread. After that, I’ll put the code that attempts to use MidiOutBus to open multiple MidiOutputs.

#pragma once
#include <JuceHeader.h>
using namespace juce;

class MidiOutBus : Thread {
public:
  MidiOutBus(
    const String& deviceName,
    const int deviceIndex) :
    Thread("MidiOutBus" + String(deviceIndex)) {
    mDeviceName = deviceName;
    mDeviceIndex = deviceIndex;
  }

  void startMidiOutBus() {
    startThread();
  }

  void stopMidiOutBus() const {
    if (isThreadRunning()) {
      notify();
    }
  }

private:
  int mDeviceIndex;
  String mDeviceName;
  std::unique_ptr<MidiOutput> mMidiOutput;
  
  void run() override {
    openMidiOutput();
    if (wait(-1)) {
      if (mMidiOutput != nullptr && mMidiOutput->isBackgroundThreadRunning()) {
        mMidiOutput->stopBackgroundThread();
        DBG("Stopped background thread for MIDI output device '" +
          mDeviceName + "'.");
      }
    }
  }

  void openMidiOutput() {
    bool foundDevice = false;
    for (const auto& device : MidiOutput::getAvailableDevices()) {
      if (device.name == mDeviceName) {
        foundDevice = true;
        break;
      }
    }
    if (!foundDevice) {
      DBG("Cannot find MIDI output device '" + mDeviceName + "'.");
      return;
    }
    DBG("Found MIDI output device '" + mDeviceName + "'.");
    // Staggering the opening of the devices, but it does not help.
    sleep(100 * mDeviceIndex); 
    //yield(); // Before or after openDevice? Either way, it does not help.
    // sleep(1000); // Sleeping instead of yielding does not help.
    mMidiOutput = MidiOutput::openDevice(mDeviceName);
    //yield(); // Before or after openDevice? Either way, it does not help.
    // sleep(1000); // Sleeping instead of yielding does not help.
    if (mMidiOutput == nullptr) {
      // If this happens, the yield or sleep has not had the desired effect.
      DBG("MIDI output device '" + mDeviceName +
        "' has been found but could not be opened.");
      return;
    }
    DBG("Opened MIDI output device '" + mDeviceName + "'.");
    // It makes no difference to whether the next MidiOutput will open
    // whether the background thread is opened or not.
    mMidiOutput->startBackgroundThread();
    DBG("Started background thread for MIDI output device '" +
      mDeviceName + "'.");
  }
};

And here’s the MidiOuManager class, which runs instances of MidiOutBus.

#pragma once
#include <JuceHeader.h>
#include "MidiOutBus.h"
using namespace juce;

class MidiOutManager {
public:
  void startOpeningMidiOuts(
    const StringArray& deviceNames) {
    mMidiOutBuses.clear();
    for (int i = 0; i < deviceNames.size(); ++i) {
      mMidiOutBuses.add(
        new MidiOutBus(
          deviceNames[i],
          i));
      mMidiOutBuses[i]->startMidiOutBus();
    }
  }

  void stopMidiOuts() {
    for (const auto midiOutBus : mMidiOutBuses) {
      midiOutBus->stopMidiOutBus();
    }
  }

private:
  OwnedArray<MidiOutBus> mMidiOutBuses;
};

And here’s the result of a test run of MidiOutManager.startOpeningMidiOuts:

Found MIDI output device '02. Internal MIDI'.
Found MIDI output device '01. Internal MIDI'.
Found MIDI output device '03. Internal MIDI'.
Opened MIDI output device '01. Internal MIDI'.
Started background thread for MIDI output device '01. Internal MIDI'.
MIDI output device '02. Internal MIDI' has been found but could not be opened.
MIDI output device '03. Internal MIDI' has been found but could not be opened.

I would appreciate any suggestions. My fallback solution would be to open and access all the MidiOutputs on the same thread. I should be able to do that with what I’ve learned already. And it may well only slow the processing down to a negligible extent. But hopefully accessing each MidiOutput on a separate thread will turn out to be a soluble problem.

I’ve started a new thread on the same subject: https://forum.juce.com/t/cannot-open-multiple-midioutputs-is-this-a-bug. Please make any further contributions in the new thread.