2

I'm seeing an intermittent crash in my app when stopping the sequencer. My app uses a custom AudioToolbox-based MusicSequencer, with the AKMIDISampler connected as its midiEndpoint. I've tracked the crash down to AKMIDISampler's func handle(event: AKMIDIEvent) getting an empty event (i.e., event.internalData.count == 0). Because the sampler is "hard-wired", so to speak, as the endpoint, I'm really not sure how I can debug this, since I can't see what the sequence is trying to send (or why it's apparently sending an empty event).

I was able to get it running by hacking my own AK build, in which I sanity check event.internalData.count. However, a separate issue in my project (https://stackoverflow.com/a/49950129/4321521) has forced me to use AudioKit from CocoaPods, which removes my fix (or rather, hack)...

I've logged all my functions that add events to the sequence and none of them appear to be sending empty data.

One possible explanation that I'm wondering about; I've recently noticed that enabling AddressSanitizer indicates a stack-buffer-overflow when creating user events. I'll admit that I'm totally mystified as to how we're intended to create variable-length events in Swift. I can't seem to "legally" create any events with event.length > 4, despite the fact that the struct has a length property. For example, this bit of code:

let eventDataBytes = ByteBackpacker.pack(event) // convert to [UInt8]
var midiData = MusicEventUserData()
midiData.length = UInt32(MemoryLayout<Event>.size)
withUnsafeMutablePointer(to: &midiData.data, { pointer in
    for i in 0 ..< eventDataBytes.count {
        print("Can write byte \(i)...")
        pointer[i] = eventDataBytes[i]
    }
})

gives me a stack-buffer-overflow error when i == 4. Since my Event has a Float64 duration property, this obviously doesn't work...

So I suppose it's inevitable that my sequence has garbage data in it, from adding these overflowed user events. I just don't see how that would lead to event.internalData.count == 0, or why my sanity check on event.internalData would "fix" it (i.e., run without issues).

The backtrace:

* thread #10, stop reason = EXC_BREAKPOINT (code=1, subcode=0x100bc677c)
    frame #0: 0x0000000100bc677c Spliqs`partial apply for closure #1 in     AKMIDISampler.enableMIDI(_:name:) [inlined] generic specialization <Swift.UInt8> of Swift.Array._getElement(Swift.Int, wasNativeTypeChecked: Swift.Bool, matchingSubscriptCheck: Swift._DependenceToken) -> A at AKMIDISampler.swift:0 [opt]
frame #1: 0x0000000100bc677c Spliqs`partial apply for closure #1 in AKMIDISampler.enableMIDI(_:name:) [inlined] generic specialization <Swift.UInt8> of Swift.Array.subscript.getter : (Swift.Int) -> A at AKMIDISampler.swift:0 [opt]
frame #2: 0x0000000100bc677c Spliqs`partial apply for closure #1 in AKMIDISampler.enableMIDI(_:name:) [inlined] AudioKit.AKMIDISampler.(event=AudioKit.AKMIDIEvent @ 0x00007f94f2693800)(event: AudioKit.AKMIDIEvent) throws -> () at AKMIDISampler.swift:60 [opt]
* frame #3: 0x0000000100bc677c Spliqs`partial apply for closure #1 in AKMIDISampler.enableMIDI(_:name:) at AKMIDISampler.swift:48 [opt]
frame #4: 0x0000000100bc677c Spliqs`partial apply for closure #1 in AKMIDISampler.enableMIDI(_:name:) at AKMIDISampler.swift:0 [opt]
frame #5: 0x0000000100bc4bb8 Spliqs`thunk for @escaping @callee_guaranteed (@unowned UnsafePointer<MIDIPacketList>, @unowned UnsafeMutableRawPointer?) -> () at AKMIDISampler.swift:0 [opt]
frame #6: 0x0000000194f6d7ac CoreMIDI`LocalMIDIReceiverList::HandleMIDIIn(unsigned int, unsigned int, void*, MIDIPacketList const*) + 156
frame #7: 0x0000000194f6d608 CoreMIDI`MIDIProcess::RunMIDIInThread() + 124
frame #8: 0x0000000194f81640 CoreMIDI`XThread::RunHelper(void*) + 20
frame #9: 0x0000000194f85698 CoreMIDI`CAPThread::Entry(CAPThread*) + 88
frame #10: 0x0000000184c25220 libsystem_pthread.dylib`_pthread_body + 272
frame #11: 0x0000000184c25110 libsystem_pthread.dylib`_pthread_start + 292
frame #12: 0x0000000184c23b10 libsystem_pthread.dylib`thread_start +
jbm
  • 1,248
  • 10
  • 22
  • Not sure what is causing the crash (although I think I've seen this before), but as a general debugging tip, avoid 'hardwiring' your sampler to your sequencer if you can. If you are able to set the MIDI endpoint to an AKCallbackInstrument, instead of the sampler, then you can use the callback function to 'manually' trigger the sampler. Among the many benefits of using the callback function is that you can add breakpoints or print statements for debugging. Also by not hardwiring the sampler, you can stop it manually before stopping the sequencer. – c_booth Sep 26 '18 at 11:09
  • 1
    Well, the crash itself is definitely caused by an empty event hitting `handleMIDI` (which reads indices without a bounds check). It's just the question of what this empty event is and how it's getting there. I'll look into the AKCallbackInstrument. Thanks for the tip. For now I've subclassed AKMIDISampler, adding the bounds check, so it's no longer crashing. – jbm Sep 26 '18 at 15:54
  • As far as I can tell, there are no advantages to directly connecting the sequencer to the sampler. On the other hand, for using AKCallbackInstrument, debugging the MIDI messages is just the tip of the iceberg in terms of what it will let you do. It will be worth your time. Here is some sample code to get you started: https://stackoverflow.com/a/50689258/2717159 – c_booth Sep 26 '18 at 18:19
  • If the callback instrument is based on user event callbacks, then this will mean porting our sequencer over to using MusicEventUserData for note events, no? I'll look into it; perhaps there's a solution that will allow us to use the AKCallbackInstrument without too much rewriting. – jbm Sep 26 '18 at 19:35
  • 1
    The callback takes good old MIDI note events (status, note number, velocity) just like AKSampler. You'll be surprised at how little you need to change. – c_booth Sep 26 '18 at 21:21
  • Interesting. I'd be curious to see how they've done that... will have to dig into the source. Will check it out, thanks! – jbm Sep 26 '18 at 21:23
  • @mrwheet That's an interesting fix. How did you implement the bounds check? – Marcus Kim Feb 27 '20 at 07:02
  • 1
    @MarcusKim, nothing smart, literally just `if event.data.count > 2 { // play it }`. But I should add that I am using AKCallbackInstruments now, as recommended by @c_booth. So far, so good. – jbm Feb 27 '20 at 07:16

0 Answers0