Skip to content

Commit

Permalink
IO: Fixed crash when calling MIDIPacketNext; refactored MIDIPacketData
Browse files Browse the repository at this point in the history
(cherry picked from commit feb7417)
  • Loading branch information
orchetect committed Apr 14, 2021
1 parent 47b8d6f commit 11f9794
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 150 deletions.
14 changes: 14 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ let package = Package(
name: "MIDIKit",
dependencies: [
.target(name: "MIDIKitCommon"),
.target(name: "MIDIKitC"),
.target(name: "MIDIKitIO"),
.target(name: "MIDIKitEvents"),
.target(name: "MIDIKitSync"),
Expand All @@ -79,6 +80,7 @@ let package = Package(
name: "MIDIKitIO",
dependencies: [
.target(name: "MIDIKitCommon"),
.target(name: "MIDIKitC"),
.product(name: "OTCore", package: "OTCore"),
.product(name: "SwiftRadix", package: "SwiftRadix")
]
Expand Down Expand Up @@ -118,6 +120,18 @@ let package = Package(
]
),

// MIDIKit common
.target(
name: "MIDIKitC",
dependencies: [
// none
],
publicHeadersPath: ".",
cxxSettings: [
.headerSearchPath(".")
]
),

// test common
.target(
name: "MIDIKitTestsCommon",
Expand Down
12 changes: 12 additions & 0 deletions Sources/MIDIKitC/CoreMIDI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// CoreMIDI.h
// MIDIKit
//
// Created by Steffan Andrews on 2021-04-13.
//

#import <Foundation/Foundation.h>
#import <CoreMIDI/CoreMIDI.h>

extern void CPacketListIterate(const MIDIPacketList *midiPacketList,
void (NS_NOESCAPE ^closure)(const MIDIPacket *midiPacket));
30 changes: 30 additions & 0 deletions Sources/MIDIKitC/CoreMIDI.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// CoreMIDI.m
// MIDIKit
//
// Created by Steffan Andrews on 2021-04-13.
//

#include "CoreMIDI.h"

NS_ASSUME_NONNULL_BEGIN

/// C method to iterate on `MIDIPacket`s within a `MIDIPacketList`
void CPacketListIterate(const MIDIPacketList *midiPacketList,
void (NS_NOESCAPE ^closure)(const MIDIPacket *midiPacket))
{

if (midiPacketList->numPackets == 0) {
return;
}

const MIDIPacket *midiPacket = &midiPacketList->packet[0];

for (UInt32 idx = 0; idx < midiPacketList->numPackets; idx++) {
closure(midiPacket);
midiPacket = MIDIPacketNext(midiPacket);
}

}

NS_ASSUME_NONNULL_END
68 changes: 13 additions & 55 deletions Sources/MIDIKitCommon/MIDIPacket/MIDIPacketData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,67 +7,25 @@

import CoreMIDI

/// Type that can iterate on raw `MIDIPacket.data` (aka `MIDIPacketRawData`)
public struct MIDIPacketData: Sequence {
/// Clean data encapsulation of a `MIDIPacket`.
public struct MIDIPacketData {

public typealias Element = Byte
/// Raw data
@inline(__always) public var data: Data

public var rawData: MIDIPacketRawData
@inline(__always) public var timeStamp: MIDITimeStamp

public var length: Int
/// Returns `[Byte]` representation `.data`.
/// - Note: accessing `.data` property is more performant.
@inline(__always) public var bytes: [Byte] { [Byte](data) }

/// Returns a [Byte] UInt8 Array of `rawData`, sized to `length`.
@inline(__always) public var array: [Byte] { Array(makeIterator()) }
@available(swift, obsoleted: 1, renamed: "bytes")
public var array: [Byte] { bytes }

/// Max byte length of `MIDIPacket` data.
@inline(__always) public static let maxLength = MIDIPacket.rawDataTupleLength

/// If `length` is nil, length will default to 256 bytes.
@inline(__always) public init(_ tuple: MIDIPacketRawData, length: Int? = nil) {

rawData = tuple

self.length = length?
.clamped(to: 0...Self.maxLength)
?? Self.maxLength

}

/// If `length` is nil, length will default to 256 bytes.
@inline(__always) public init(_ tuple: MIDIPacketRawData, length: UInt16? = nil) {

self.init(tuple, length: length?.int)

}

@inline(__always) public init(_ midiPacket: MIDIPacket) {

self.init(midiPacket.data, length: midiPacket.length)

}

/// Custom iterator for `MIDIPacket`
@inline(__always) public func makeIterator() -> AnyIterator<Byte> {

AnyIterator<Byte>(
Mirror(reflecting: rawData)
.children
.lazy
.prefix(length)
.map { $0.value as! Byte }
.makeIterator()
)

}

}

extension MIDIPacket {

/// Converts an instance of `MIDIPacket` to `MIDIPacketData`
@inline(__always) public var packetData: MIDIPacketData {
@inline(__always) public init(data: Data, timeStamp: MIDITimeStamp) {

MIDIPacketData(self)
self.data = data
self.timeStamp = timeStamp

}

Expand Down
50 changes: 0 additions & 50 deletions Sources/MIDIKitCommon/MIDIPacket/MIDIPacketRawData.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ extension MIDIIO.ReceiveHandler {
_ srcConnRefCon: UnsafeMutableRawPointer?
) {

packetListPtr.pointee
.forEach { handler($0.packetData) }
packetListPtr.forEach { handler($0) }

}

Expand Down Expand Up @@ -159,9 +158,9 @@ extension MIDIIO.ReceiveHandler {
_ srcConnRefCon: UnsafeMutableRawPointer?
) {

for packet in packetListPtr.pointee {
for packet in packetListPtr {

let bytes = packet.packetData.array
let bytes = packet.data

if filterActiveSensingAndClock {
guard bytes.first != 0xF8, // midi clock pulse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,90 @@
//

import CoreMIDI
import MIDIKitC

extension MIDIPacketList {
/// aka `UnsafePointer<MIDIPacketList>`
/// allows iteration on the pointer directly, ie:
///
/// func parse(_ ptr: UnsafePointer<MIDIPacketList>) {
/// for packet in ptr {
/// // ...
/// }
/// }
///
extension UnsafePointer: Sequence where Pointee == MIDIPacketList {

public typealias Element = MIDIPacketData

public typealias Iterator = PacketListIterator

/// Create a generator from the packet list
@inline(__always) public func makeIterator() -> Iterator {

Iterator(self)

}

/// Custom iterator to iterate `MIDIPacket`s within a `MIDIPacketList`.
public struct PacketListIterator: IteratorProtocol {

public typealias Element = MIDIPacket
public typealias Element = MIDIPacketData

var index = 0

var count: UInt32
var index: Int = 0
var packet: MIDIPacket
var packets: [Element] = []

/// Initialize the packet list generator with a packet list
/// - parameter packetList: MIDI Packet List
init(_ packetList: MIDIPacketList) {
@inline(__always) init(_ packetListPtr: UnsafePointer<MIDIPacketList>) {

self.packet = packetList.packet
self.count = packetList.numPackets
CPacketListIterate(packetListPtr) {
guard let unwrappedPtr = $0 else { return }
packets.append(stupidWorkaround(unwrappedPtr))
}

}

@_optimize(none)
public mutating func next() -> Element? {
@inline(__always) public mutating func next() -> Element? {

// On Intel and PowerPC, MIDIPacket is unaligned.
// On ARM, MIDIPacket must be 4-byte aligned
// MIDIPacketNext(...) takes care of this.
guard !packets.isEmpty else { return nil }

guard index < count else { return nil }
guard index < packets.count else { return nil }

if index > 0 {
packet = MIDIPacketNext(&packet).pointee
}
index += 1
defer { index += 1 }

return packet
return packets[index]

}

}

@inline(__always) fileprivate
static let MIDIPacketDataOffset: Int = MemoryLayout.offset(of: \MIDIPacket.data)!

@inline(__always) fileprivate
static func stupidWorkaround(_ packetPtr: UnsafePointer<MIDIPacket>) -> MIDIPacketData {

let packetDataCount = Int(packetPtr.pointee.length)

guard packetDataCount > 0 else {
return MIDIPacketData(
data: .init(),
timeStamp: packetPtr.pointee.timeStamp
)
}

// This workaround is needed due to a variety of crashes that can occur when either the thread sanitizer is on, or uncommon MIDI packet lists / packets arrive
let rawMIDIPacketDataPtr = UnsafeRawBufferPointer(
start: UnsafeRawPointer(packetPtr) + MIDIPacketDataOffset,
count: packetDataCount
)

return MIDIPacketData(
data: Data(rawMIDIPacketDataPtr),
timeStamp: packetPtr.pointee.timeStamp
)

}

}
22 changes: 0 additions & 22 deletions Sources/MIDIKitIO/MIDIPacketLIst/MIDIPacketList.swift

This file was deleted.

0 comments on commit 11f9794

Please sign in to comment.