Bug with MidiOutput and CCs when sent to IAC Driver

On macOS Monterey, with JUCE 7.0.5.

Issue

Certain CC numbers are not sent out to IAC Driver ports.

How to Reproduce

  • In the Audio MIDI Setup app, ensure that at least one IAC Driver port is active.
  • Install the snoize MIDIMonitor tool. Open it, and check “MIDI sources”.
  • Create a MidiDemo project from JUCE/examples/Audio
  • In the handleNoteOn method of MidiDemo, change the MidiMessage line to:

MidiMessage m (MidiMessage::controllerEvent (midiChannel, midiNoteNumber, velocity));

  • For clarity, also comment out all of the handleNoteOff method.

  • Now build MidiDemo and select an IAC Driver in its “MIDI Output” list. Scroll all the way to bottom of the onscreen keyboard.

  • Press the lowest note (C -2), which will trigger a CC0. It does not appear in MIDIMonitor.

  • Press the next note up (C# -2), which will trigger a CC1. It does appear in MIDIMonitor.

A thorough iteration through all the CC messages reveals that the complete list of CC messages that don’t appear in MIDIMonitor is: 0, 6, 32, 38, 98 - 101.

Conclusions

This sounds possibly related to the CC MIDI Sending Bug issue reported here.

However, the notable differences include:

  • This affects only IAC Driver ports on my system. Output to MIDI hardware devices (as observed in MIDIMonitor using its “Spy on output to destinations” options), or using MIDIMonitor as its own virtual port (using its "“Act as a destination for other programs” option), shows that all 128 CCs are passed just fine.

Also, as is noted in that previously reported issue, when I send the same CC messages to an IAC Driver port using a test patch with Cycling '74 Max, they all appear OK in MIDIMonitor. So it is not a systemwide problem with all MIDI in macOS.

If I try to send a CC0 event to the IAC Driver on Monterey, then I can confirm that the message doesn’t get through. In fact, the message gets filtered out by JUCE before it ever reaches CoreMidi.

The reason for this is that the IAC driver endpoint has a kMIDIPropertyProtocolID of kMIDIProtocol_2_0, which means that it expects messages to use MIDI 2.0 protocol. Looking at the UMP specs (which you can find here), appendix D.3.3 specifies that individual use of controllers CC 6, 38, 98, 99, 100, and 101 do not translate to the MIDI 2.0 Protocol, unless they are properly formed RPN/NRPN messages, and individual use of controllers CC 0 and CC 32 shall not translate to the MIDI 2.0 Protocol, unless they are used in a MIDI 2.0 Protocol Program Change message with the Bank Valid bit set. These conditions aren’t met in the test-case you described, so it’s invalid to send these messages.

I downloaded a copy of Max 8 and tried attaching a debugger and setting a symbolic breakpoint on a few of the CoreMidi functions. As far as I can tell, they’re still using the old (exclusively MIDI 1.0) MIDIPacketList API, which is why you see different behaviour in that application.

I suppose that JUCE should attempt to negotiate the IAC endpoint into MIDI 1.0 protocol mode by default, but as I understand it the original way of doing this using Capability Inquiry has been deprecated already. I’ll try to do some testing next week to see whether this is a viable path forward.

1 Like

I vote for this! :slight_smile:

Thanks for looking into this. I understand that the MIDI 2.0 spec has stricter requirements about the use of those CC numbers.

As a developer, I would prefer JUCE to use MIDI 1.0 by default, as you are suggesting. We can’t necessarily negotiate the use of MIDI 1.0 for every MIDI endpoint, but for the IAC driver, I expect it to act as a transparent conduit for MIDI, not as a filter.

I would also request some asserts in the JUCE code, if invalid messages are being sent to a MIDI 2.0 endpoint. It took quite a while to track down the source of the error in this case.

1 Like

Followup - I added this assert to my fork of JUCE. In Midi1ToMidi2DefaultTranslator.h, at line 91:

if (processControlChange (helperValues, packet))
	callback (View (packet.data()));
else
	jassertfalse;  // if you hit this, it means a MIDI 1.0 CC message wasn't able to be translated to MIDI 2.0
1 Like

I’ve investigated this a bit more now. Previously, JUCE was converting all MIDI messages into the ‘native’ format of the destination device, as advertised by CoreMIDI. However, it seems that this isn’t necessary. If MIDI 1.0 protocol events are sent to a device that is using MIDI 2.0 protocol natively, then CoreMIDI translates these events appropriately - even for MIDI 1.0 events that can’t be represented in the MIDI 2.0 protocol.

Now, JUCE will always send events using the MIDI 1.0 protocol, and leave translation to CoreMIDI.

2 Likes

If that works to leave the translation to CoreMIDI then great! Thanks for fixing this.