Skip to content

Commit

Permalink
Merge pull request #2 from dnadoba/fix-allocatePackageList
Browse files Browse the repository at this point in the history
Fix allocate package list
  • Loading branch information
dnadoba authored Sep 22, 2020
2 parents 733bf6a + 6dce481 commit a88ed9c
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 35 deletions.
90 changes: 55 additions & 35 deletions Sources/MIDIKit/MIDIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,32 +356,44 @@ public class MIDIOutputPort {
}
}

private let sizeOfMIDIPacketList = MemoryLayout<MIDIPacketList>.size
private let sizeOfMIDIPacket = MemoryLayout<MIDIPacket>.size

/// The `MIDIPacketList` struct consists of two fields, numPackets(`UInt32`) and
/// packet(an Array of 1 instance of `MIDIPacket`). The packet is supposed to be a "An open-ended
/// array of variable-length MIDIPackets." but for convenience it is instaciated with
/// one instance of a `MIDIPacket`. To figure out the size of the header portion of this struct,
/// we can get the size of a UInt32, or subtract the size of a single packet from the size of a
/// packet list. I opted for the latter.
private let sizeOfMIDIPacketListHeader = sizeOfMIDIPacketList - sizeOfMIDIPacket

/// The MIDIPacket struct consists of a timestamp (`MIDITimeStamp`), a length (`UInt16`) and
/// data (an Array of 256 instances of `Byte`). The data field is supposed to be a "A variable-length
/// stream of MIDI messages." but for convenience it is instaciated as 256 bytes. To figure out the
/// size of the header portion of this struct, we can add the size of the `timestamp` and `length`
/// fields, or subtract the size of the 256 `Byte`s from the size of the whole packet. I opted for
/// the former.
private let sizeOfMIDIPacketHeader = MemoryLayout<MIDITimeStamp>.size + MemoryLayout<UInt16>.size
private let sizeOfMIDICombinedHeaders = sizeOfMIDIPacketListHeader + sizeOfMIDIPacketHeader

extension Sequence where Element == MIDIMessage {
public func allocatePackageList(timeStamp: MIDITimeStamp = 0) -> UnsafePointer<MIDIPacketList> {
let sizeOfAlleMessages = self.size
assert(sizeOfAlleMessages <= Int(UInt16.max), "allocatePackageList does not support messages bigger than \(UInt16.max)")
let packetListSize = MemoryLayout<MIDIPacketList>
.offset(of: \MIDIPacketList.packet.data) ?? 0 + sizeOfAlleMessages
let packetListSize = sizeOfMIDICombinedHeaders + sizeOfAlleMessages

let packetListPointer = UnsafeMutableRawPointer
.allocate(byteCount: packetListSize,
alignment: MemoryLayout<MIDIPacketList>.alignment)
defer { packetListPointer.deallocate() }
let packetList = packetListPointer.assumingMemoryBound(to: MIDIPacketList.self)
packetList.pointee.numPackets = 1

let packetPointer = packetListPointer
.advanced(by: MemoryLayout<MIDIPacketList>.offset(of: \MIDIPacketList.packet)!)
let packet = packetPointer.assumingMemoryBound(to: MIDIPacket.self)

packet.pointee.timeStamp = timeStamp
packet.pointee.length = UInt16(sizeOfAlleMessages)

let data = packetPointer
.advanced(by: MemoryLayout<MIDIPacket>.offset(of: \MIDIPacket.data)!)
.assumingMemoryBound(to: UInt8.self)
let packetList = packetListPointer.assumingMemoryBound(to: MIDIPacketList.self)

write(to: data)
let packet = MIDIPacketListInit(packetList)
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
defer { buffer.deallocate() }
write(to: buffer)
MIDIPacketListAdd(packetList, packetListSize, packet, timeStamp, size, UnsafePointer(buffer))

return UnsafePointer(packetList)
}
}
Expand Down Expand Up @@ -436,6 +448,31 @@ extension MIDIInputConnection: Hashable {
}
}

extension MIDIPacketList {
public func parse(using parser: inout MIDIParser) -> [Result<[MIDIMessage], Error>] {
withUnsafePointer(to: self) { (packetList) in
let packetCount = numPackets
var packet = UnsafeRawPointer(packetList)
.advanced(by: MemoryLayout<MIDIPacketList>.offset(of: \MIDIPacketList.packet)!)
.assumingMemoryBound(to: MIDIPacket.self)
return (0..<packetCount).map { _ -> Result<[MIDIMessage], Error> in

let data = UnsafeRawPointer(packet)
.advanced(by: MemoryLayout<MIDIPacket>.offset(of: \MIDIPacket.data)!)
.assumingMemoryBound(to: UInt8.self)

let bytes = UnsafeBufferPointer<UInt8>(start: data, count: Int(packet.pointee.length))

let result = Result { try parser.parse(data: bytes) }

packet = UnsafePointer(MIDIPacketNext(packet))

return result
}
}
}
}

public class MIDIInputPort {
public typealias Value = MIDIPackage
public typealias ReadBlock = (Result<Value, Error>) -> ()
Expand All @@ -450,28 +487,11 @@ public class MIDIInputPort {
assertionFailure("endpointRef ist nil")
return
}

var packet = UnsafeRawPointer(packetList)
.advanced(by: MemoryLayout<MIDIPacketList>.offset(of: \MIDIPacketList.packet)!)
.assumingMemoryBound(to: MIDIPacket.self)
let packetCount = packetList.pointee.numPackets
let timeStamp = packet.pointee.timeStamp
let timeStamp = packetList.pointee.packet.timeStamp

let parsedPacket = queue.sync { () -> [Result<[MIDIMessage], Error>] in
let connection = UnsafeRawPointer(endpointRef).assumingMemoryBound(to: MIDIInputConnection.self).pointee
return (0..<packetCount).map { _ -> Result<[MIDIMessage], Error> in
let data = UnsafeRawPointer(packet)
.advanced(by: MemoryLayout<MIDIPacket>.offset(of: \MIDIPacket.data)!)
.assumingMemoryBound(to: UInt8.self)

let bytes = UnsafeBufferPointer<UInt8>(start: data, count: Int(packet.pointee.length))

let result = Result { try connection.parser.parse(data: bytes) }

packet = UnsafePointer(MIDIPacketNext(packet))

return result
}
return packetList.pointee.parse(using: &connection.parser)
}

queue.async {
Expand Down
21 changes: 21 additions & 0 deletions Tests/MIDIKitTests/MIDIMessageToBytesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,25 @@ class MIDIMessageToBytesTests: XCTestCase {
testWriteAndParserMessages(makeMessages(channel: 0, data0: 127, data1: 127))
testWriteAndParserMessages(makeMessages(channel: 15, data0: 127, data1: 127))
}

func testWriteToMessageListe() throws {
func testWriteAndParserMessages(_ messages: [MIDIMessage]) throws {
let messageList = messages.allocatePackageList()
defer { messageList.deallocate() }
var parser = MIDIParser()

XCTAssertEqual(messages, try messageList.pointee.parse(using: &parser).flatMap{ try $0.get() })
}
try testWriteAndParserMessages(makeMessages(channel: 0, data0: 0, data1: 0))
try testWriteAndParserMessages(makeMessages(channel: 1, data0: 0, data1: 0))
try testWriteAndParserMessages(makeMessages(channel: 0, data0: 1, data1: 0))
try testWriteAndParserMessages(makeMessages(channel: 0, data0: 0, data1: 1))
try testWriteAndParserMessages(makeMessages(channel: 15, data0: 0, data1: 0))
try testWriteAndParserMessages(makeMessages(channel: 0, data0: 127, data1: 0))
try testWriteAndParserMessages(makeMessages(channel: 0, data0: 0, data1: 127))
try testWriteAndParserMessages(makeMessages(channel: 15, data0: 0, data1: 127))
try testWriteAndParserMessages(makeMessages(channel: 15, data0: 127, data1: 0))
try testWriteAndParserMessages(makeMessages(channel: 0, data0: 127, data1: 127))
try testWriteAndParserMessages(makeMessages(channel: 15, data0: 127, data1: 127))
}
}

0 comments on commit a88ed9c

Please sign in to comment.