Сбой AKMIDISampler при остановке секвенсора

Я вижу периодический сбой в моем приложении при остановке секвенсора. В моем приложении используется пользовательский MusicSequencer на основе AudioToolbox с AKMIDISampler, подключенным в качестве его midiEndpoint. Я отследил сбой до того, что AKMIDISampler func handle(event: AKMIDIEvent) получил пустое событие (то есть event.internalData.count == 0). Поскольку сэмплер, так сказать, "зашит" как конечная точка, я действительно не уверен, как я могу это отладить, так как я не вижу, что последовательность пытается отправить (или почему она, по-видимому, отправляет пустое событие).

Я смог запустить его, взломав собственную сборку AK, в которой я проверил работоспособность event.internalData.count. Однако отдельная проблема в моем проекте (https://stackoverflow.com/a/49950129/4321521) заставила меня использовать AudioKit от CocoaPods, удаляющий мой фикс (точнее, хак)...

Я зарегистрировал все свои функции, которые добавляют события в последовательность, и ни одна из них не отправляет пустые данные.

Одно возможное объяснение, о котором мне интересно; Недавно я заметил, что включение AddressSanitizer указывает на переполнение буфера стека при создании пользовательских событий. Признаюсь, я совершенно озадачен тем, как мы собираемся создавать события переменной длины в Swift. Кажется, я не могу «легально» создавать какие-либо события с event.length > 4, несмотря на то, что структура имеет свойство length. Например, этот кусок кода:

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]
    }
})

дает мне ошибку переполнения буфера стека, когда i == 4. Поскольку у моего события есть свойство Float64 duration, это, очевидно, не работает...

Поэтому я полагаю, что моя последовательность неизбежно содержит мусорные данные из-за добавления этих переполненных пользовательских событий. Я просто не понимаю, как это может привести к event.internalData.count == 0, или почему моя проверка работоспособности на event.internalData "исправит" это (т. е. запустится без проблем).

Обратный след:

* 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 +

person jbm    schedule 26.09.2018    source источник
comment
Не уверен, что вызывает сбой (хотя я думаю, что видел это раньше), но в качестве общего совета по отладке избегайте «жесткого подключения» вашего сэмплера к секвенсору, если это возможно. Если вы можете установить конечную точку MIDI на AKCallbackInstrument вместо сэмплера, то вы можете использовать функцию обратного вызова, чтобы «вручную» запустить сэмплер. Одним из многих преимуществ использования функции обратного вызова является то, что вы можете добавлять точки останова или печатать операторы для отладки. Кроме того, не подключая сэмплер, вы можете остановить его вручную перед остановкой секвенсора.   -  person c_booth    schedule 26.09.2018
comment
Ну, сам сбой определенно вызван пустым событием, попадающим в handleMIDI (которое считывает индексы без проверки границ). Вопрос только в том, что это за пустое событие и как оно туда попадает. Я посмотрю на AKCallbackInstrument. Спасибо за чаевые. На данный момент я создал подкласс AKMIDISampler, добавив проверку границ, чтобы он больше не давал сбоев.   -  person jbm    schedule 26.09.2018
comment
Насколько я могу судить, прямого подключения секвенсора к сэмплеру нет никаких преимуществ. С другой стороны, при использовании AKCallbackInstrument отладка MIDI-сообщений — это лишь верхушка айсберга с точки зрения того, что она позволит вам сделать. Это будет стоить вашего времени. Вот пример кода для начала: stackoverflow.com/a/50689258/2717159   -  person c_booth    schedule 26.09.2018
comment
Если инструмент обратного вызова основан на обратных вызовах пользовательских событий, то это будет означать перенос нашего секвенсора на использование MusicEventUserData для событий нот, не так ли? Я посмотрю на это; возможно, есть решение, которое позволит нам использовать AKCallbackInstrument без лишнего переписывания.   -  person jbm    schedule 26.09.2018
comment
Обратный вызов принимает старые добрые события MIDI-нот (статус, номер ноты, скорость), как и AKSampler. Вы будете удивлены тем, как мало вам нужно изменить.   -  person c_booth    schedule 27.09.2018
comment
Интересно. Мне было бы любопытно посмотреть, как они это сделали... придется копаться в источнике. Проверим, спасибо!   -  person jbm    schedule 27.09.2018
comment
@mrwheet Это интересное исправление. Как вы реализовали проверку границ?   -  person Marcus Kim    schedule 27.02.2020
comment
@MarcusKim, ничего умного, буквально просто if event.data.count > 2 { // play it }. Но я должен добавить, что сейчас я использую AKCallbackInstruments, как рекомендовал @c_booth. Все идет нормально.   -  person jbm    schedule 27.02.2020