CoreMIDI Callbacks in Swift
Asked Answered
S

1

7

I am using the following code to receive MIDI events in a Swift Playground:

import Cocoa
import CoreMIDI
import XCPlayground

XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)

func notifyCallback(message:UnsafePointer<MIDINotification>,refCon:UnsafeMutablePointer<Void>)
{
    println("MIDI Notify")
}

func eventCallback(pktlist:UnsafePointer<MIDIPacketList>, refCon:UnsafeMutablePointer<Void>, connRefCon:UnsafeMutablePointer<Void>)
{
    println("MIDI Read")
}

var client = MIDIClientRef()
MIDIClientCreate("Core MIDI Callback Demo" as NSString, MIDINotifyProc(COpaquePointer([notifyCallback])), nil, &client)

var inPort = MIDIPortRef()
MIDIInputPortCreate(client, "Input port",MIDIReadProc(COpaquePointer([eventCallback])), nil, &inPort)

let sourceCount = MIDIGetNumberOfSources()
for var count:UInt = 0; count < sourceCount; ++count
{
    let src:MIDIEndpointRef = MIDIGetSource(count)
    MIDIPortConnectSource(inPort, src, nil)
}

I got this by translating working Objective-C code into what I think would be the correct Swift version.

It compiles and runs fine until one of the callbacks fires, e.g. when I unplug the MIDI device or hit one of its keys. I always get a BAD_EXEC.

Any ideas how to make this work or is Swift just not ready as some blog posts on the web state. Anything official from Apple that I overlooked that clearly states Swift is not ready yet for CoreMIDI callbacks?

Update 2015-03-10: The corresponding Objective-C code can be found at http://www.digital-aud.io/blog/2015/03/10/on-coremidi-callbacks/

Update 2021-06-25: I have written a programming tutorial on how to receive MIDI messages on Apple devices https://twissmueller.medium.com/midi-listener-in-swift-b6e5fb277406. There is a link in the tutorial where a complete Xcode-project with a sample application can be purchased.

Simulate answered 8/3, 2015 at 9:10 Comment(7)
Similar questions: #26925834, #27709414, #27690634. My guess it that C function callbacks are just not supported (yet) in Swift.Crinum
Yup, but am not sure how much changed since then regarding swift. it really seems like its not suppoerted yet, also found this very good post: rockhoppertech.com/blog/swift-and-c-api-callbacks.Simulate
Thanks for the comment, Tobias. I just tried again in the Swift 1.2 beta that was pushed out this week. No luck still. Maybe if we all open an issue with Apple about function pointers that would motivate them? bugreport.apple.comConsultative
I know this is obvious, but the callback problem is for all C APIs, not just Core MIDI. CGPathApplierFunction is another example that you cannot do in Swift yet.Consultative
Thanks a lot, Gene. Have filed a bug report with Apple (20185404). hopefully, when more people will do so, this will get things going.Simulate
The link to your blogpost which requires purchasing the Xcode project should be identified as such.Thurgau
HenryRootTwo, I have added the info to my post.Simulate
F
3

Swift3 seems to have better support for CoreMIDI than earlier releases. The example playground demo shown below shows some working rudimentary CoreMIDI calls.

import Cocoa
import CoreMIDI
import PlaygroundSupport

// helper method to extract the display name from a MIDIObjectRef
func midiObjectDisplayName(_ obj: MIDIObjectRef) -> String {

    var param: Unmanaged<CFString>?
    var capturedName = "Error"
    let err = MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &param)
    if err == OSStatus(noErr) {
      capturedName = param!.takeRetainedValue() as String
    }
    return capturedName
}

// method to collect display names of available MIDI destinations
func midiDestinationNames() -> [String] {
    var names:[String] = []

    let count:Int = MIDIGetNumberOfDestinations()

    for i in 0..<count {
        let endpoint:MIDIEndpointRef = MIDIGetDestination(i)
        if  endpoint != 0 {
            names.append(midiObjectDisplayName(endpoint))
        }
    }
    return names
}

let destinationNames = midiDestinationNames()

// check if we have any available MIDI destinations.
if destinationNames.count > 0 {

    // establish a MIDI client and output port, and send a note on/off pair.
    var midiClient:MIDIClientRef = 0
    var outPort:MIDIPortRef = 0

    MIDIClientCreate("Swift3 Test Client" as CFString, nil, nil, &midiClient)
    MIDIOutputPortCreate(midiClient, "Swift3 Test OutPort" as CFString, &outPort)

    let destNum = 0
    let destName = destinationNames[destNum]
    var dest:MIDIEndpointRef = MIDIGetDestination(destNum)

    var midiPacket:MIDIPacket = MIDIPacket()
    midiPacket.timeStamp = 0
    midiPacket.length = 3
    midiPacket.data.0 = 0x90 + 0 // Note On event channel 1
    midiPacket.data.1 = 0x3D // Note Db
    midiPacket.data.2 = 100 // Velocity

    var packetList:MIDIPacketList = MIDIPacketList(numPackets: 1, packet: midiPacket)
    print("Sending note on to \(destName)")
    MIDISend(outPort, dest, &packetList)

    midiPacket.data.0 = 0x80 + 0 // Note off event channel 1
    midiPacket.data.2 = 0 // Velocity
    sleep(1)
    packetList = MIDIPacketList(numPackets: 1, packet: midiPacket)
    MIDISend(outPort, dest, &packetList)
    print("Note off sent to \(destName)")
} 
Fourpence answered 2/1, 2017 at 22:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.