How to Use MIDI in Swift to Play Chords in Your iOS/Mac app

Carlos Mbendera
CodeX
Published in
4 min readJul 26, 2023

--

Hello there, if you’ve ever wanted to use MiDi in your own music app or simply want to add a musical touch to your iOS/Mac project, you might have come across MIDI(Musical Instrument Digital Interface).

In this article, I’ll explain how you can somewhat “easily”implement MIDI in Swift. We’ll be using Apple’s AudioToolbox framework.

Photo by GESPHOTOSS on Unsplash

Show Me The Code

Let’s begin with a brief overview of the basic implementation.

import AudioToolbox

First, import AudioToolbox. We will mainly use the AudioToolbox framework to manage and play MIDI sequences.

We’re going to create a createMusicSequence function that receives an array of chords, each represented as an array of UInt8 values (MIDI note numbers). The function then constructs a music sequence with these chords.

If you don’t want to read each step then skip to the bottom and see the complete function.


func createMusicSequence(chords: [[UInt8]] ) -> MusicSequence {
//Magic Here

Initialise an empty MusicSequence

var musicSequence: MusicSequence?
var status = NewMusicSequence(&musicSequence)

Next, set up a tempo track:

var tempoTrack: MusicTrack?
if MusicSequenceGetTempoTrack(musicSequence!, &tempoTrack) != noErr {
assert(tempoTrack != nil, “Cannot get tempo track”)
}

Here, MusicSequenceGetTempoTrack retrieves the tempo track from the music sequence. The tempo track determines the speed of the music. You can set up different tempos at different points in the music sequence. In other words, the tempo track is responsible for the BPM (Beats Per Minute) of the sequence.

Now, we create a new track and adds each note in each chord to the track:

var track: MusicTrack?
status = MusicSequenceNewTrack(musicSequence!, &track)

var beat: MusicTimeStamp = 0.0
for batch in 0..<chords.count {
for note: UInt8 in chords[batch] {
var mess = MIDINoteMessage(channel: 0, note: note, velocity: 64, releaseVelocity: 0, duration: 1.0)
status = MusicTrackNewMIDINoteEvent(track!, beat, &mess)
}
beat += 1
}

Each MIDINoteMessage specifies the properties of a MIDI note event, such as the note pitch (note), the intensity of the note (velocity), and the duration of the note (duration).

Finally, the function prints the music sequence to the console for debugging and returns it:


CAShow(UnsafeMutablePointer<MusicSequence>(musicSequence!))

return musicSequence!

Behold the complete function:

func createMusicSequence(chords: [[UInt8]] ) -> MusicSequence {

var musicSequence: MusicSequence?
var status = NewMusicSequence(&musicSequence)
if status != noErr {
print(" bad status \(status) creating sequence")
}

var tempoTrack: MusicTrack?
if MusicSequenceGetTempoTrack(musicSequence!, &tempoTrack) != noErr {
assert(tempoTrack != nil, "Cannot get tempo track")
}

//MusicTrackClear(tempoTrack, 0, 1)
if MusicTrackNewExtendedTempoEvent(tempoTrack!, 0.0, 128.0) != noErr {
print("could not set tempo")
}
if MusicTrackNewExtendedTempoEvent(tempoTrack!, 4.0, 256.0) != noErr {
print("could not set tempo")
}


// add a track
var track: MusicTrack?
status = MusicSequenceNewTrack(musicSequence!, &track)
if status != noErr {
print("error creating track \(status)")
}



// make some notes and put them on the track
var beat: MusicTimeStamp = 0.0

for batch in 0..<chords.count {
for note: UInt8 in chords[batch] {
var mess = MIDINoteMessage(channel: 0,
note: note,
velocity: 64,
releaseVelocity: 0,
duration: 1.0 )
status = MusicTrackNewMIDINoteEvent(track!, beat, &mess)
if status != noErr { print("creating new midi note event \(status)") }

}// beat changes after this
beat += 1
}

CAShow(UnsafeMutablePointer<MusicSequence>(musicSequence!))

return musicSequence!
}

Calling The Function

We are going to write a small short function that uses all the code we wrote above so stay with me.

func createPlayer(chords : [[UInt8]]){
var musicPlayer : MusicPlayer? = nil
var player = NewMusicPlayer(&musicPlayer)

player = MusicPlayerSetSequence(musicPlayer!, createMusicSequence(chords: chords))
player = MusicPlayerStart(musicPlayer!)
}

Here is how we’d use it out in the wild.

let chords: [[UInt8]] = [[60, 64, 67], [62, 65, 69], [64, 67, 71]]
// C, D, and E major chords
createPlayer(chords: chords)

Now an explanation. This function, createPlayer(chords: [[UInt8]]), accepts an array of arrays as input. Each inner array represents a chord, and each element in this array is a UInt8 representing a MIDI note.

Then we initialise a new MusicPlayer instance and bind it to the musicPlayer variable. Next, we set the music sequence for our player using MusicPlayerSetSequence(). The sequence is created using the helper function createMusicSequence(chords: chords). Finally, we start the MusicPlayer instance with MusicPlayerStart().

Photo by Kopfhörer Events Deutschland on Unsplash

✨Yay! We did it!✨

✨Congratulations✨

Example Projects:

Here are a few projects I made using this implementation as a foundation.

--

--

Carlos Mbendera
CodeX

CS Major,  WWDC23 Student Challenge Winner and Jazz Fusion Enthusiast writing about Swift and other rad stuff.