How to Use MIDI in Swift to Play Chords in Your iOS/Mac app
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.
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()
.
✨Yay! We did it!✨
✨Congratulations✨
Example Projects:
Here are a few projects I made using this implementation as a foundation.