我正在研究一些使用核心midi的midi输出的例子。
和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
答案 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 字节。
这个例子中的编码非常高效
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个字节。