Thomas
Thomas

Reputation: 151

Using MIDIPacketList in swift

I am looking at some of the examples of midi output using core midi.

Specifically this question

and this

I have code that works based on these in objC and I now want to try to translate that to swift.

the line I least understand is this one: MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;

I read this as declaring a pointer pktlist of type MIDIPacketList and assigning it the value of pktBuffer, cast to the type MIDIPacketList

I'm new to C and objC and this makes no sense to me.

MIDIPacketList is a struct defined here:

in what way does it make sense to cast a bytearray to the struct type MIDIPacketList and what is this trying to achieve? I guess it is trying to define the size of the packet list, but why do we need to do that here, and how does this do it anyway? why is it not sufficient to do it with MIDIPacketListAdd as happens a few lines later?

here is my attempt at a midi output class in swift - can anyone see what is going wrong? this code gives no errors in Xcode until it is run. I have a working version of this in objC, but i can't get the size of the packet list defined in swift. (at least I think that's the problem)

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

Upvotes: 8

Views: 4580

Answers (4)

Moose
Moose

Reputation: 2737

Swift 5 version, and probably the right way to encode midi packets.

First, esoteric pointer initialisations and memory rebounds are not needed to construct a MidiPacketList. Simply create your list the straight way and fill it.

This code pack midi events objects into a MidiPacketList. It works only with common musical events - See the Midi Protocol Specifications to deal with all types of events like sysex messages.

  • In most cases, only one packet is needed since the data size limit is 65536 bytes, which is largely enough for everything but sysex. Send a noteOn for all notes in height octaves would need 1 status byte + 96 * 2 bytes for note number and velocity. 193 bytes.

  • The encoding in this example is quite performant

    • no function calls in the array init
    • no multiple calls to MIDIPacketListAdd
    • takes the running status in account to make messages as compact as possible.
  • To take advantage of the running status, it is best to sort midi events by types. So all notes on are grouped first, and then controls.

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
}

Here is the MidiEvent class used in the example.

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

Note that MidiPacketList this is deprecated since MacOS 11 and MidiEventList should be preferred to be compatible with midi protocol 2.0. The principle is the same, but alignment is 4 bytes.

Upvotes: 0

ephemer
ephemer

Reputation: 1334

This is how I ended up going about it. I used the example of the byte buffer as found in various C / ObjC examples online, but I found I had to allocate a little bit more space for the MIDIPacketList struct itself.

A MidiEvent is just a UInt8 array. For usual MIDI events, it has a length of 3 bytes: (Status, Data, Data).

/// 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
    }
}

So if you're just sending one midi note, it'd look something like this. This example would send a NoteOn message to the Middle C on Channel 1 with a velocity of 100. You should use helper functions to make these MidiEvents though, rather than hard coding them ;)

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

Upvotes: 2

Ted van Gaalen
Ted van Gaalen

Reputation: 1169

I have also used the above routine for sending midi commands from my app to internal synths running in the background like iMini, Z3TA+, Sunrizer, etc. but I couldn't get rid of memory leaks. This version has no unsafe pointers and allocs. It works without the dreaded memory leaks.

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
}

tested.

Upvotes: 0

Thomas
Thomas

Reputation: 151

I found an answer at this question

the byte array in the objC version is a nasty hack to allocate some memory to the packetList

here's my revised code, which now works.

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

However, the commented out dealloc seems to be unnecessary, I'm not exactly sure why yet. Does MIDIReceived take care of it?

If anyone has a better solution - maybe without using pointers at all, please post it!

Upvotes: 7

Related Questions