BR: Hanging notes with AU wrapper

Since the release of Logic 10.7.0 we’ve had dozens of complaints about hanging notes and notes playing even though there is no midi input at that time (midi-chase?).

This has been corroborated by other JUCE developers here:

This is really urgent. It seems EVERY single JUCE plugin is affected. This is a showstopper that affects thousands of our customers.

What sort of plugins cause issues for you? Are you seeing issues with MIDI FX, or with synth plugins, or something else? With AUv2 or AUv3? And, on what OS version? Testing today, I’ve seen quite different behaviours in MIDI FX plugins between the same version of Logic running on macOS 12.0.1 and macOS 12.1 (Arm)…

I just tried out the steps from the linked post with the AudioPluginDemo in Logic 10.7.2, both as AUv2 and AUv3, and native/Rosetta, all under macOS 12.1. (To get the AudioPluginDemo AUv3 to load properly in Logic on Monterey it is necessary to disable the MIDI output due to another Monterey bug). That is, I put the AudioPluginDemo synth on an instrument channel, created a MIDI region with a single long note, and then tried playing the project then seeking to a specific location. Every time I tried, the seek action caused the plugin to receive a note-off matching the active note, followed by an ‘all notes off’ controller change. I did not observe any stuck notes.

I’m seeing a few problems (including unexpected note messages) with MIDI FX AUs in Logic, but I haven’t been able to trigger any stuck notes with a plain synth plugin yet. To investigate further, it would be useful to have the information I mentioned in my previous post, along with a test procedure that triggers the issue.

Edit: Here’s some more info about the MIDI FX issues I was seeing. I was testing using JUCE’s MIDILogger demo, which just passes through all MIDI messages. With two of these plugins in series, I was seeing that the second plugin sometimes logged additional unexpected messages (including note-ons). These always seemed to follow a big block of sysex data, which Logic sends to each MIDI FX plugin after playback starts. It seems that the first plugin in the chain tries to pass this sysex message through to the second plugin, but the message gets mangled in transit. To test this theory, I tried adding a button to the MIDILogger to send an equivalent block of sysex data directly. Now, when I send the sysex block from the first plugin, the second plugin never receives it, but instead receives several garbage messages including note-ons and aftertouch messages. It seems that Logic’s processing of MIDI outputs doesn’t handle sysex properly in 10.7.2. I’m reasonably confident this is a Logic bug; you may be able to work around it by filtering sysex messages from the output of MIDI FX AU plugins.

Sorry for the missing information:

  1. It’s AUv2
  2. It’s a synth (Nexus)
  3. macOS Big Sur and Monterey
  4. Intel and Arm

We have trouble reproducing it ourselves, but it does happen from time to time. There are customers for whom it happens all the time.

1 Like

Do you happen to know whether those customers are using Nexus and third-party midi effects on the same channel strip? Additionally, are you able to find whether they have other non-JUCE non-built-in synth plugins that also exhibit the issue?

I’ve tried a bit more to reproduce the problem on my M1 macbook (native and rosetta) and my old Intel one, both running Logic 10.7.2 and macOS 12.1 this morning, but without any luck. Without being able to reproduce this issue, my best guess is that Logic itself is failing to send note-offs, or sending spurious note-ons. The AUv2 MIDI input code is pasted below; I can’t see anywhere in the wrapper where the incoming events might be modified accidentally.

    OSStatus HandleMidiEvent (UInt8 nStatus, UInt8 inChannel, UInt8 inData1, UInt8 inData2, UInt32 inStartFrame) override
    {
       #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect
        const juce::uint8 data[] = { (juce::uint8) (nStatus | inChannel),
                                     (juce::uint8) inData1,
                                     (juce::uint8) inData2 };

        const ScopedLock sl (incomingMidiLock);
        incomingEvents.addEvent (data, 3, (int) inStartFrame);
        return noErr;
       #else ...

If the problem really is with Logic, there’s not really anything we can do about it in JUCE. Reproducing the issue in a non-JUCE, non-internal plugin would be a clear indicator that the problem is in Logic itself.

If you’re able to find a consistent repro, please let me know and I’ll investigate further.

We just received another report. It also happens with 10.7.2. I will try to find out more about the user’s setup.

No, nothing like that. In my own repos (with 10.7.0, right after the first reports) there was nothing on the channel strip at all. Only Nexus.

The way to repo it was to create a 4 bar midi-region and but a 4 bar note in there. Once the note was playing, click with the mouse above the midi-clip, where you normally would drag the song-position marker, to make it jump to a new position behind the midi-clip. Instead of sending a note-off, nothing would happen and the note would continue to play.

I saw videos of other, similar, problems in which notes would be triggered even the song position hadn’t reached the midi-regions, yet.

Could it be that the time-stamps for these events are outside the audio buffer (so numbers are negative or larger than block-size) and thus get maybe filtered out by JUCE before they reach the process block?

1 Like

I think this is incredibly unlikely. I posted the MIDI input callback above - as you can see, we immediately add each incoming message to incomingEvents, and we swap incomingEvents with the previous event buffer before processBlock is called. MidiBuffer::addEvent does not filter events, so all events that were received prior to a processBlock call should be visible within the next processBlock.

If Logic were sending message with timestamps outside the current block, then these events should be visible within the processBlock call, and I think it would be up to the AudioProcessor implementation to handle these appropriately.

I just tried adding some logging to the AudioPluginDemo to see whether it was receiving note events outside the current audio block.

Interestingly, I’m able to reproduce the hanging note issue now, when I wasn’t able to before. I’m using the laptop speakers rather than headphones now, so perhaps the audio device or sampling rate has something to do with triggering the issue.

I tried adding some logging to print out the received midi messages in the plugin (attaching a debugger to Logic on M1 is still awkward). When I trigger a hanging note, I see that the plugin is just receiving a normal “Note on” message, with no matching “Note off”. FWIW I’m logging from directly in HandleMidiEvent, and the inStartFrame values look reasonable (normally 0, otherwise non-negative and within the buffer size). I also tried adding similar logging directly before processBlock and the message timestamps match up. I’m inclined to say that the issue is on Logic’s side.

Does JUCE handle this the right way?

https://developer.apple.com/forums/thread/670069

I think it does, yes. As far as I can tell, MIDI events for AU plugins always reach the plugin through the AUMethodMIDIEvent callback, which accepts the status byte, data bytes, and timestamp all as individual arguments. I don’t see anywhere in that code where we might accidentally be mangling the timestamp or event types, for example.

The AU utility classes do also have a HandleMIDIPacketList function that accepts a PacketList and unpacks it in the way outlined in the thread you linked. However, this function doesn’t seem to actually be called anywhere. Even if there did happen to be a bug in the implementation of this function, I don’t think it would affect JUCE plugins because the function doesn’t seem to be used.

I can confirm that this is a known issue on Apple Silicon in the Smart Splitpoint processing code. Sorry about that. We obviously want to fix this, but I can’t provide you with a date.

If I remember correctly, the code does use mach_timebase_info, but accidentally numerator and denominator were switched around. That is why you do not see the issue on Intel CPUs (both values of numer and denom are 1), but on PowerPC (for the older folks: denom was the bus clock, so it was either 33333335MHz or 25000000MHz with numer being 1000000000) and Apple Silicon (it seems the values are e.g. numer = 125, denom = 3). It results in a time calculation being way off. You might want to check your code for that as well for similar bugs. See: Technical Q&A QA1398: Mach Absolute Time Units and Apple Developer Documentation

7 Likes