在swift中使用MIDIPacketList

时间:2014-10-21 19:11:05

标签: objective-c swift casting midi coremidi

我正在研究一些使用核心midi的midi输出的例子。

具体为this question

this

我的代码在objC中基于这些工作,我现在想尝试将其转换为swift。

我最不了解的是这一行:MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;

我读这个是声明一个MIDIPacketList类型的指针pktlist,并为它赋值pktBuffer,强制转换为类型MIDIPacketList

我是C和objC的新手,这对我来说毫无意义。

MIDIPacketList是一个定义为here的结构:

以什么方式将bytearray强制转换为结构类型MIDIPacketList,这是什么意思?我想它正在尝试定义数据包列表的大小,但为什么我们需要在这里做到这一点,无论如何这样做呢?为什么用MIDIPacketListAdd来做这件事就好了几行呢?

这是我在swift中尝试midi输出类 - 有人能看出出了什么问题吗?在运行之前,此代码在Xcode中不会出错。我在objC中有一个工作版本,但我无法获得swift中定义的数据包列表的大小。 (至少我认为这是问题所在)

import Foundation
import CoreMIDI

class MidiOutClass {

var midiClient = MIDIClientRef()
var midiSource = MIDIEndpointRef()


func openOutput() {
    MIDIClientCreate("MIDI client", nil, nil, &midiClient)
    MIDISourceCreate(midiClient, "MIDI Source",&midiSource)
    println("midi out opened")//should only do this if successful
}

func noteOn(channel: Int, note: Int, velocity:Int) {
    midisend((0x90+channel), note: note, value: velocity)
}

func polyAfter(channel: Int, note: Int, value:Int) {
    midisend((0xA0+channel), note: note, value: value)
}

func noteOff(channel: Int, note: Int) {
    midisend((0x90+channel), note: note, value: 0 )
}

func midisend(status:Int, note: Int, value:Int) {

    var packet: UnsafeMutablePointer<MIDIPacket> = nil
    //var buffer = [Byte](count:1024, repeatedValue: 0)
//this is the array I'm trying to use in a similar way to the obj C.

    var packetList: UnsafeMutablePointer<MIDIPacketList> = nil
    let midiDataToSend:[Byte] = [Byte(status), Byte(note), Byte(value)];
    packet = MIDIPacketListInit(packetList);
    packet = MIDIPacketListAdd(packetList, 1024, packet, 0, 3, midiDataToSend);

    if (packet == nil ) {
        println("failed to send the midi.")
    } else {
        MIDIReceived(midiSource, packetList)
        println("sent some stuff")
    }
}

}//end MidiOutClass

4 个答案:

答案 0 :(得分:5)

我找到了答案at this question

objC版本中的字节数组是一个讨厌的hack来为packetList分配一些内存

这是我修改后的代码,现在可以使用了。

    func midisend(status:Int, note: Int, value:Int) {

    var packet = UnsafeMutablePointer<MIDIPacket>.alloc(1)
    var packetList = UnsafeMutablePointer<MIDIPacketList>.alloc(1)
    let midiDataToSend:[Byte] = [Byte(status), Byte(note), Byte(value)];
    packet = MIDIPacketListInit(packetList);
    packet = MIDIPacketListAdd(packetList, 1024, packet, 0, 3, midiDataToSend);

    if (packet == nil ) {
        println("failed to send the midi.")
    } else {
        MIDIReceived(midiSource, packetList)
        println("sent some stuff")
    }
    packet.destroy()
    //packet.dealloc(1)
    packetList.destroy()
    packetList.dealloc(1)
}

然而,注释掉的dealloc似乎是不必要的,我还不确定为什么。 MIDIReceived会照顾它吗?

如果有人有更好的解决方案 - 可能根本没有使用指针,请发布!

答案 1 :(得分:1)

这就是我最终的目的。我使用了在线的各种C / ObjC示例中的字节缓冲区示例,但我发现我必须为MIDIPacketList结构本身分配更多的空间。

MidiEvent只是一个UInt8阵列。对于通常的MIDI事件,它的长度为3个字节:(状态,数据,数据)。

/// A UInt8 array, usually 3 bytes long
public typealias MidiEvent = [UInt8]

extension MIDIPacketList {
    init(midiEvents: [MidiEvent]) {

        let timestamp = MIDITimeStamp(0) // do it now
        let totalBytesInAllEvents = midiEvents.reduce(0) { total, event in
            return total + event.count
        }

        // Without this, we'd run out of space for the last few MidiEvents
        let listSize = MemoryLayout<MIDIPacketList>.size + totalBytesInAllEvents

        // CoreMIDI supports up to 65536 bytes, but in practical tests it seems
        // certain devices accept much less than that at a time. Unless you're
        // turning on / off ALL notes at once, 256 bytes should be plenty.
        assert(totalBytesInAllEvents < 256,
               "The packet list was too long! Split your data into multiple lists.")

        // Allocate space for a certain number of bytes
        let byteBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: listSize)

        // Use that space for our MIDIPacketList
        self = byteBuffer.withMemoryRebound(to: MIDIPacketList.self, capacity: 1) { packetList -> MIDIPacketList in
            var packet = MIDIPacketListInit(packetList)
            midiEvents.forEach { event in
                packet = MIDIPacketListAdd(packetList, listSize, packet, timestamp, event.count, event)
            }

            return packetList.pointee
        }

        byteBuffer.deallocate() // release the manually managed memory
    }
}

所以,如果你只是发送一个midi音符,它看起来像这样。此示例将向通道1上的中间C发送NoteOn消息,速度为100.您应该使用辅助函数来制作这些MidiEvent,而不是对它们进行硬编码;)

var packets = MIDIPacketList(midiEvents: [[0x90, 60, 100]])
MIDISend(clientOutputPort, destination, &packetList)

答案 2 :(得分:0)

我还使用上面的例程将midi命令从我的应用程序发送到后台运行的内部合成器,如iMini,Z3TA +,Sunrizer等,但我无法摆脱内存泄漏。这个版本没有不安全的指针和分配。它没有可怕的内存泄漏。

static var z: UInt8 = 0  // initalize tuple with 256 x this UInt8 value:

// Silly: why not an array instead of this.. a tuple is needed.. length must be exact 256..
// Don't know no other way to create a tuple with 256 elements...
  var midiDataTuple = (z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z, z)

// use the above tuple in MIDIPacket



func midiSend(status: Int, val1: Int, val2 :Int)
{
    var midipacket = MIDIPacket()

    midipacket.timeStamp = 0
    midipacket.length    = 3
    midipacket.data      = midiDataTuple  //<-=

    midipacket.data.0 = UInt8(status)
    midipacket.data.1 = UInt8(val1  )
    midipacket.data.2 = UInt8(val2  )

    var midipacketlist = MIDIPacketList(numPackets: 1, packet: midipacket)

    MIDIReceived(midiSource, &midipacketlist)
}




//////////////////////////////////////////////////////////////
func noteOn(soundNr: Int, note: Int, velocity:Int)
{
    let chan = midiChannelForSoundNr[soundNr]
    midiSend((0x90 + chan), val1: note, val2: velocity)
    notesPlaying[chan][note] = true
}

测试。

答案 3 :(得分:0)

Swift 5 版本,可能是对 MIDI 数据包进行编码的正确方法。

首先,构造 MidiPacketList 不需要深奥的指针初始化和内存回弹。只需直接创建列表并填写即可。

此代码将 MIDI 事件对象打包到 MidiPacketList 中。它仅适用于常见的音乐事件 - 请参阅 Midi 协议规范以处理所有类型的事件,例如 sysex 消息。

  • 在大多数情况下,只需要一个数据包,因为数据大小限制为 65536 字节,这对于除 sysex 之外的所有内容都足够了。 为高度八度音阶的所有音符发送 noteOn 需要 1 个状态字节 + 96 * 2 个字节用于音符编号和速度。 193 字节。

  • 这个例子中的编码非常高效

    • 数组 init 中没有函数调用
    • 不能多次调用 MIDIPacketListAdd
    • 获取帐户中的运行状态,使消息尽可能紧凑。
  • 为了利用运行状态,最好按类型对midi事件进行排序。所以所有的音符都先分组,然后控制。

public static func packEvents(_ events: [MidiEvent]) throws -> MIDIPacketList? {
    let numberOfEvents: UInt32 = UInt32(events.count)
    guard numberOfEvents > 0 else { return nil }
    
    var dataSize: Int = 0
    // We could preflight the events list to only allocate the needed size, but the overhead is not worth it, since majority of musical events will be 3 bytes long
    let bytes = [UInt8].init(unsafeUninitializedCapacity: 3 * Int(numberOfEvents)) { (pointer, count) in
        // The number of data bytes in the message
        var numBytes = 0
        // The status byte of the last event
        // According to MIDI protocol running status, we don't have to repeat the status if
        // type and channels are equal ( status byte equals )
        var runningStatus: UInt8 = 0
        for event in events {
            let status: UInt8 = (event.type.rawValue & 0xF0) | (event.channel & 0x0F)
            // Encode status if needed
            if status != runningStatus {
                runningStatus = status
                pointer[numBytes] = status
                numBytes += 1
            }
            // Encode values
            if event.numberOfDataBytes > 0 {
                pointer[numBytes] = event.value1
                numBytes += 1
            }
            if event.numberOfDataBytes > 1 {
                pointer[numBytes] = event.value2
                numBytes += 1
            }
        }
        dataSize = numBytes
        count = numBytes
    }
    
    var outPackets = MIDIPacketList()
    var writePacketPtr = MIDIPacketListInit(&outPackets)
    MIDIPacketListAdd(&outPackets, Int(14 + dataSize), writePacketPtr, 0, dataSize, bytes)
    
    return outPackets
}

这是示例中使用的 MidiEvent 类。

public enum MidiEventType: UInt8 {
    case noteOff = 0x80
    case noteOn = 0x90
    case polyAfterTouch = 0xA0
    case control = 0xB0
    case programChange = 0xC0
    case afterTouch = 0xD0
    case pitchBend = 0xE0
    case clock = 0xF0

    // The data length that follows the (Type|Channel) byte
    public var dataLength: UInt8 {
        switch self {
        case .noteOff, .noteOn, .pitchBend, .control, .polyAfterTouch:
            return 2
        case .afterTouch, .programChange:
            return 1
        case .clock:
            return 0
        }
    }
}

public struct MidiEvent: CustomStringConvertible {
    public let type: MidiEventType
    public let timestamp: UInt64
    public let channel: UInt8
    public let value1: UInt8
    public let value2: UInt8

    public var numberOfDataBytes: UInt8
    
    // The mask to apply to data[0] to get type and channel
    static let channelMask: UInt8 = 0x0F
    static let typeMask: UInt8 = 0xF0
    
    public init(type: MidiEventType, timestamp: UInt64 = 0, channel: UInt8, value1: UInt8, value2: UInt8 = 0) {
        self.type = type
        self.timestamp = timestamp
        self.channel = channel
        self.value1 = value1
        self.value2 = value2
        self.numberOfDataBytes = type.dataLength
    }
}

请注意,MidiPacketList 已被弃用,因为 MacOS 11 和 MidiEventList 应首选与 MIDI 协议 2.0 兼容。原理一样,只是对齐是4个字节。