Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoreMIDI Callbacks in Swift

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.

like image 210
Tobias Avatar asked Nov 22 '22 00:11

Tobias


1 Answers

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)")
} 
like image 134
Mark Semsel Avatar answered Dec 07 '22 23:12

Mark Semsel