Cannot open multiple MidiOutputs - Is this a bug?

I hope this is OK, but this is a new thread on a subject covered in a thread I created two weeks ago: https://forum.juce.com/t/cannot-open-multiple-midiouts. Not only did I make a typo I cannot correct in the old thread title, I drew conclusions in that thread that have been proven by my subsequent investigations to have been incorrect.

But the problem remains the same. I cannot find a way to open multiple MidiOutputs, or at least not in a way that I can do any processing with them. I’ve developed this test project, which shows several attempts to solve the problem and documents how they fail:
MidiOutputTest.zip (61.7 KB)

I would appreciate it if anyone would run the project and see if the problem is replicated. I’m developing on Windows, so perhaps my code will behave differently on other operating systems or perhaps even on a different computer.

I hope this does not demonstrate a JUCE bug. Either way I hope someone can show me a way to make this work.

How to use the test project

You will need at least two MIDI output devices to connect to. If you are on Windows and happen to have the internal MIDI ports provided by LoopBe30, you can use the ones that are currently hard-coded. (I hit the same problem with the virtual ports that are available with Bome’s MIDI Translator Pro. So the problem is not specific to the ports. And I need to be able to use virtual/internal MIDI ports.) To change the MIDI output device names, go to the top of MainComponent's constructor.

The names of the test classes containing the code to open the devices are shown in the application’s Select a Test combo box. When you select a test, some details of the specific test are shown below the combo box.

Click Start to run a test and Stop before running another test or closing the application, though as explained in the test-specific notes, the Stop button only does any real work in the last test. When a test is run, diagnostics are shown below the buttons and, in most tests, one or more error message boxes are shown.

I have written many apps that open multiple midi ports, so it does work. Modulo any breaking change in JUCE since I built any of those apps.

I was going to look at your code, but I see there is a fair amount in there. Is it possible you could whittle the problem down to the minimal lines of code required to reproduce the problem? Maybe even something with a GUI?

@cpr2323, its a good point that there’s a lot in the my test program. I think if I provide some orientation, you will find that there’s very little you need to look at.

The Main Example

Though there are six examples, you could just look at the code for the last one, the MidiOutputBusOnePerThread class. It’s the only example that accesses each MidiOutput in its own thread, something I would prefer to do in the real application I’m working on. MidiOutputBusOnePerThread::run starts the multiple individual device threads, which are implemented in the MidiOutputDevice class.

The Base Class

As I wanted to show multiple examples, MidiOutputBusOnePerThread and all the other test classes derive from an abstract class, MidiOutputBusBase. This base class contains nothing of substance for accessing the MidiOutputs.

MidiOutputBusBase::startMidiOutputDevices is called when the Start button is clicked, in MainComponent::startButtonClicked. It stores the device names for later access via getDeviceNameCount, getDeviceName and getDeviceNames. Then it starts the thread. It is not virtual, as it does not need to be overridden in any of the derived classes.

MidiOutputBusBase::stopMidiOutputDevices is called when the Stop button is clicked, in MainComponent::stoptButtonClicked. In the base class, this method does nothing. But it is overridden in MidiOutputBusOnePerThread.

The GUI

There are many GUI classes, copied from my real application to provide style. But the only code that is relevant to processing MidiOutputs is in MainComponent. As mentioned in my first post, the device names are specified at the top of its constructor. And I described MainComponent::startButtonClicked and MainComponent::stoptButtonClicked above. I should just add that MainComponent::startButtonClicked also initiates the Timer usage, which is specific to MidiOutputBusOnePerThread.

Going Forward

Hopefully you will now have enough information to check my code with ease. However, if you find that a cut-down demo application would still be valuable, please let me know and I’ll make one. Or let me know if you have any questions. I really appreciate your help.

Sorry brother, doing full-time software development along with my own projects only leaves so much space in my head for new things. I want to be able to reason about your code, so I need as little as possible. Hopefully someone else can take a look using your notes. :slightly_smiling_face:

No worries. I’ll make a single file demo application. You are welcome to look at that if it works out for you. I’m onto it.

@cpr2323, here’s the single file demo application:
MidiOutputSimple.zip (44.3 KB)

It only contains only one usage example, based on the MidiOutputBusField example of the more fulsome demo application. I chose that example because it is less complex than MidiOutputBusOnePerThread. The problem to be solved is essentially the same in both. All the code is in MainComponent.h.

Perfect size! Looking over the code I don’t see anything obvious. Which leads me to suggest jumping into the debugger and stepping into the second call to MidiOutput::openDevice (ie. the first failure). I quickly looked at the Windows version of that juce code, and most of the failures relate to OS calls. I think understanding where that failure is would be the next step in diagnosing this. Does the failure occur if you don’t start/stop the background thread? If that isn’t required for the failure you should remove it from this test code.

The failure occurs irrespective of whether the background thread is started/stopped. In the fulsome edition of the demo application, I had a comment that said so. In my zeal to cut down the code size, I left the comment out! You are right, of course, the good thing to do would have been to remove the start/stop altogether. I’ve done that in this new version:
MidiOutputSimple.zip (44.6 KB)

Following your suggestion, I have stepped into the JUCE code with the debugger. In MidiOutput::openDevice, the problem occurs at this line:

wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier));

From there, I stepped into Win32MidiService::createOutputWrapper which contains just one line:

return new Win32OutputWrapper (*this, deviceIdentifier);

So I stepped into Win32OutputWrapper's constructor. Within that, for both the first device and the second device (the one that always fails to open) the code gets to the following line:

for (int i = parent.activeOutputHandles.size(); --i >= 0;)

That is the line that does not behave as expected. parent.activeOutputHandles is an Array of MidiOutHandles. Regardless of how many MIDI output devices my computer has, or which devices I want to open, or the order in which I specify the required devices, parent.activeOutputHandles only ever contains one MidiOutHandle. That MidiOutHandle's device identifier is queried in the following line:

if (activeHandle->deviceInfo.identifier == deviceIdentifier)

The MidiOutHandle's device identifier always turns out to be that of the first device I asked to open. In my hard-coded list, that’s ‘01. Internal MIDI’, even when the device being looked for (as specified by the deviceIdentifier parameter of Win32OutputWrapper's constructor) is ‘02. Internal MIDI’. I cannot usefully step further into either of the last quoted two lines, as they only access data items.

After failing to find a match for the device identifier, the traversal through the Win32OutputWrapper constructor code ends up here:

throw std::runtime_error ("Failed to create Windows output device wrapper");

But the exception is caught. So, rather than crash the program, MidiOutput::openDevice returns nullptr.

I wondered if the problem had to do with the fact that I’m trying to open internal/virtual MIDI devices. So I substituted a list of three MIDI devices that are actually physical ports on USB hardware controllers. The failure was exactly the same with the physical devices.

All I can usefully conclude is that

  • The failure occurs in Windows-specific JUCE code, so this may not be a problem in macOS etc.
  • And it does not matter what kind of devices I try to open or in what order: only the first device will open.

Can you run my code in Windows please Chris? It would be good to confirm whether you can replicate the problem. I’m running Windows 10, in case the Windows version turns out to be relevant. If you happen to have another operating system you can run this on, that would be great too. I’ve only got Windows.

1 Like

I will check it out later today. I suspect this is a potential MIDI driver issue though.

Are you using a virtual MIDI driver and if so, which one?

Hello @oli1. I’ve got two virtual MIDI drivers, LoopBe v1.6 and Bome’s MIDI Translator Pro v1.9.0. In addition, I have two other MIDI Drivers, Korg USB MIDI Driver v1.15 and Novation USB MIDI Driver 2.24.1.75.

For good measure, I’ve just uninstalled all four and, after rebooting my PC, repeated the test. Unfortunately, the error was the same.

I should perhaps also mention that I’ve also coded multiple MIDI outputs using NAudio, an audio library for .Net that works only with Windows. If there’s something specific to my MIDI software environment that JUCE cannot cope with, NAudio works fine with it. I’m attempting to replace the NAudio MIDI with JUCE, so that I can make my application cross-platform.

Hello again @oli1. I realised I also had the MIDI drivers than come with Native Instruments Komplete Kontrol 2.6.7.1. So I got rid of that too. It has made no difference to the test results. I know you only mentioned virtual MIDI drivers. I’m just removing these others just in case. My audio interface driver, MOTU Pro Audio 2.90067 is the only other driver I can think of that provides MIDI capability. As you can imagine, I’m reluctant to uninstall it, but I will if you want me to.

Great work! Being able to get under the hood is another reason I love JUCE. It is an interesting failure case, and you are right on track with wanting to verify the problem on another system. I can run the code, but it may not be for a day or two. Hopefully @oli1 can get you going more quickly! :slight_smile:

Just had a quick look at your code. MidiOutputWrapper constructor parameter semantics is invoking the MidiOutput destructor because of incorrect copy/move semantics being used.

This might work if you used an rvalue reference (&&) and std::move, but really you should be using pointers.

If a JUCE method returns a pointer (raw or smart) you should assume that it shouldn’t be dereferenced unless you have a good reason to.

MidiOutputWrapper::MidiOutputWrapper(MidiOutput& midiOutput) {
  this->midiOutput.reset(&midiOutput); 
}

Should be:

MidiOutputWrapper::MidiOutputWrapper(std::unique_ptr<MidiOutput>&& outputPort) {
  this->midiOutput = std::move(outputPort);
}
1 Like

@oli1, thanks for the tip about the MidiOutputWrapper constructor. Yes, that looks much better! However, as you say,

Understood, and, as you will have seen, I avoided dereferencing the smart pointer in the other examples. So, unsurprisingly, the change to the MidiOutputWrapper constructor did not fix the problem.

Did you test my code and not reproduce the problem? That could be one of the reasons for your question about virtual MIDI drivers.

We are likely to sometimes have time lags in communication, as I am in New Zealand and you are probably in the UK. In that case, I’ll be writing this well after your working day has finished, whereas I am going to have breakfast when I’ve written this! However, I shall still be available to correspond this evening my time, which will be be your morning.

Morning Simon,

I did run your project, and it failed to open the ports, So I put together a minimal example that successfully opened all ports on my machine.

std::vector<std::unique_ptr<MidiOutput>> ports;
        
for (const auto& port : MidiOutput::getAvailableDevices())
{
    if (auto p = MidiOutput::openDevice (port.identifier))
    {
        p->startBackgroundThread();
                
        Thread::sleep (1000);
                
        if (p->isBackgroundThreadRunning())
            ports.push_back (std::move (p));
    }
}

jassert (ports.size() == MidiOutput::getAvailableDevices().size());

Thanks, @oli1! I’ve figured out my big mistake. I was passing the device’s name as a parameter to MidiOutput::openDevice when I should have been passing the device’s identifier. The manual does clearly state that the identifier is what is needed.

What confused me was the strange behaviour whereby MidiOutput::openDevice would always open the first device whose name I mistakenly gave it, no matter which particular device it was. What’s that all about, I wonder? If it had failed to open any devices or opened only those few devices that, I notice, have the same name as identifier, I would probably have been forced to work out my mistake. RTFM.

Problem solved!

2 Likes