Issues with MidiBuffer in JUCE 7 on macOS in RELEASE build

My system: macOS Monterey 12.6, M2

After migrating to JUCE 7.02, I am experiencing issues with the MidiBuffer class when building for RELEASE, both for the VST3 plugin and the standalone app. The issue does not appear with the DEBUG build. It doesn’t appear on Windows at all. Similar issues occur on an Intel chip. I didn’t observe that issue before with JUCE 6.

The issue: I am adding MidiMessages (NoteOns and NoteOffs) to the MidiBuffer in processBlock(). The MIDI data going in makes perfect sense. The RELEASE build, however, seems to be sending all kind of other messages to the DAW host, eventually forcing the host to crash on access violation. It also happens that note-off events are missed, resulting in never-ending notes.

I am also using the LinkedListPointer class to dynamically (new and delete) manage internal events in that context.

Again, I have no complaints on my Windows machine (x64). It neither seems to be an M2 issue alone. After many trials, I came to the conclusion that this is a JUCE 7 BUG that should be addressed. If it is not, please advise on what I may be doing wrong.

1 Like

I’m not seeing any issues with this, perhaps you could provide some concrete information and clarify “sending all kind of other messages”

Not sure how much details you need, but let me be more specific. This is the basic code of the function where MIDI events are added to the MIDI buffer:

void DummyAudioProcessor::someSubFunctionInsideProcessBlock(MidiBuffer& midiBuffer)
{
	auto* curr = m_eventList.get();

	while (curr != nullptr)
	{
		if (curr->type != EventType::NOTE_ON)
		{
			curr = curr->nextListItem.get();
			continue;
		}
    }

    auto* next = curr->nextListItem.get();

    while (next != nullptr)
	{
		if (next->type != EventType::NOTE_OFF || next->noteNumber != curr->noteNumber)
		{
			next = next->nextListItem.get();
			continue;
		}
	}

    if (curr->type == EventType::NOTE_ON)
	{
		midiBuffer.addEvent(MidiMessage::noteOn(1, curr->noteNumber, (uint8)curr->velocity), (int)m_deltaTime);
    }

    if (next != nullptr)
	{
		midiBuffer.addEvent(MidiMessage::noteOff(1, next->noteNumber), (int)m_deltaTime);

        m_eventList.remove(next);
        delete next;

        next = curr->nextListItem.get();
        m_eventList.remove(curr);
        delete curr;
        
        curr = next;
        continue;
      }
    }

    curr = curr->nextListItem.get();
  }
}

There is more logic around it in terms of timing, but this is irrelevant here, I’d say. The code works perfectly fine on Windows, and there are no memory leaks. When I debug the standalone app on macOS, everything seems to work fine. To be able to see whether there might be something wrong with the MidiMessage parameters in RELEASE mode, I log them. Everything makes perfect sense. Therefore, I conclude that the issue is in how the MidiBuffer class handles those MidiMessages and how JUCE communicates with the host via the IAC Driver.

The weird stuff that happens in the host is:

  • Notes are not ended correctly (missing NoteOffs), leading to hanging notes;
  • The note number in NoteOns is outside the original range (way too high or too low), leading to hanging notes that weren’t supposed to be there;
  • NoteOns and NoteOffs outside of the allowed range (uint8), leading to access violations [this is my interpretation].
  • Some elements in the host’s UI are triggered as if a certain key combination was sent instead of a MIDI message, as for example the activation of a different MIDI track.

I have looked into the JUCE code and I’ve noticed that you use different classes and functions for time calculation, etc. Is there something that’s “over-optimized” that may be causing this?

It looks like curr might be null on this line.

    auto* next = curr->nextListItem.get();

Perhaps the function should early-exit before that line if curr is null.

In general, you can track down these kinds of issues easily by enabling Address Sanitizer (ASAN) on your project, and re-running the steps that trigger the problem. When running under a debugger, the program will print a detailed explanation of any memory errors that are encountered. For plugins, it’s easiest to build the plugin and host with address sanitizer enabled - the JUCE AudioPluginHost is easy to rebuild with Address Sanitizer support.

ASAN can be enabled by adding -fsanitize=address to your compiler and linker flags in the Projucer, then re-saving and re-building the project.

1 Like

Thank you, reuk. I know your advice is well intentioned. next is expected to be null at when it reaches the end of the list. I think I mentioned above that there are no memory leaks that could be traced down. The code comiles and runs flawlessly on Windows.

Did you try what I recommended? ASAN clearly shows that there is a problem in the code you provided:

I fear the issue is more complex than a correctly implemented while loop that itrates on a linked list. The code I provided is incomplete, as I mentioned above, and was only intended to give an idea of what I am doing.

To reinterate: There are no reports of memory leaks when I debug and everything seems to work just fine. In RELEASE, however, things get pretty ugly. I saw there were reports by others that their plugins would exhibit similar issues of everlasting notes. I didn’t see any solution to this. Is this an acknowledged issue of JUCE 7 and is a bug fix to be released any time soon? Does anyone know?

This is not a known issue in JUCE 7, and there’s not enough information provided to be able to determine whether the issue is on JUCE’s side. Given that there haven’t been other similar reports, and that MIDI handling works as expected in the example VST3 plug-ins, I think the issue is unlikely to be in JUCE.

As I mentioned above, the best way to proceed is to try testing with ASAN enabled. Then, on any memory issues (including access violations), the debugger will print a detailed stack trace explaining the problem. This stack trace will help a JUCE developer to pinpoint the issue.

Alternatively, you could duplicate your project, remove as much as possible while preserving the buggy behaviour, and then provide a copy of this project for a JUCE developer to debug.

It seems to be very much related to this: Bugs in M1 Logic Pro Itself. Is it an acknowledged Apple issue then? I did run the standalone app with all possible ASAN settings enabled in Xcode, but couldn’t find anything (so far). I am neither using Logic Pro for testing, so it is not a Logic Pro issue I would think.

The issue you linked is only present in Logic. It doesn’t affect MIDI handling elsewhere, and certainly won’t have any effect on VST3 plug-ins.

Sorry, @reuk, but you finally lost me. What others are describing here, Garbage MIDI Data (MIDI FX, Logic Pro, Mac M1), is quite identical with my observation – just with a different host. It may be an Apple Silicon issue, but nobody seems to know for sure. I guess, I’ll have to figure out on my own.

That issue refers to a bug in Logic. If you’re not testing in Logic, then the issue you’re seeing must be unrelated, even if the symptoms are similar.

Generally if you see a bug in Release but not in Debug then you should look for an uninitialized variable. I have run into an issue compiling a single function in Xcode which causes an issue only in Release when running under Rosetta (not native arm64) and was caused by the Xcode optimizer… the only way to fix the issue was to use __attribute__ ((optnone)) for the MAC build for that single function.

a) look for uninitialized variables
b) turn off optimization in your Release build

If the issue persists you probably have an issue with your code.

Rail

I deleted my previous post because my happiness didn’t last long. The plugin seemed to work for some hours but then all problems came back. (This might indicate that the issue is clock-related.)

MY SOLUTION
Previously, I compiled the plugin as a universal binary (x86_64 and arm64), but now I compile it for x86_64 (Intel) only and run the DAW in Rosetta mode. This works now under macOS Ventura as it already did under Windows 11.

THE PROBLEM
The problem, in my opinion, is in the JUCE framework. I recompiled the plugin with JUCE 6.1.6 and the behavior was the same as with JUCE 7.0.2. The problem appears when compiling for the Silicon chip in Release mode and making use of the MidiBuffer class in the processBlock() function. The problem does not appear in Debug mode. My guess is that there are defines and optimizations in the JUCE framework that are at the source of the problem (-Ox has no impact): The MIDI massages don’t seem to be sent in a correct format to the host via the IAC Driver. These functions should be revised by the developers and eventually fixed in a new release.

Otherwise, I am just happy that I finally got it running after weeks of debugging (my code) and despair.
:sweat: :weary:

P.S. I have a strong suspicion that the issue with Logic Pro that I cited further above has the same root cause, although I couldn’t verify it.