Play MIDI Notes in Swift’s Playgrounds
Sounds good to me!
Prerequisites:
- Coding in Swift Playgrounds (guide HERE)
A Singleton is covered in slight detail, some might benefit from the background information in a more in-depth article
Terminology
AudioToolbox: A framework to record and play audio
MIDI (Music Instrument Digital Interface): A standard for musical instruments, and can be used to play MIDI instruments on Apple Devices
MusicSequence: The music sequence previously attached to the audio engine
MusicTrack: Several music events, each timestamped using a series of beats
Motivation
I’ve created several Apps that require a small piece of audio to play (a *bing* or *chime*. I’ve experimented with AVPlayer
and AVAudioPlayer
, and looking around on the web for public domain chimes.
It wasn’t that fun I’ll admit that. The performance wasn’t where I wanted it to be, and the quality of the samples I used.
Well.
I’m a better developer now (probably). I can find a better solution (probably).
Why didn’t I think of MIDI before?
MIDI Audio — In a Playground
Look, Swift has you well covered in terms of providing AudioToolbox
, and of course this Framework is avaliable in Playgrounds. This is excellent news!
The Playground setup
Now since audio playback is asynchronous we will have to pull out one of our best tricks, that is the playground needsIndefiniteExecution
. This means that our playground needs two lines of code:
import PlaygroundSupport
and
PlaygroundPage.current.needsIndefiniteExecution = true
It is with this in place, we can approach the challenge with a light heart and a full brain (or something).
The MIDI steps
Now don’t panic, there is a full playground at the end of this section of the article to help you out, and right here is a step-by-step explanation of how each section of code works.
With that clear, let us press on.
Create a new music sequence and a track
var sequence : MusicSequence? = nil
var musicSequenceStatus: OSStatus = NewMusicSequence(&sequence)
var track : MusicTrack? = nil
var musicTrack = MusicSequenceNewTrack(sequence!, &track)
var time = MusicTimeStamp(1.0)
A musicSequenceStatus
is simply an OSStatus making sure that NewMusicSequence
has created the 120 beats-per-minute tempo track on the sequence
. The track
is where will will lay down our mean beats (play the MIDI instruments).
So then we add an (empty) track to the sequence, again returning an OSStatus.
time as a MusicTimeStamp
refers to time values in the sequence, and is measured in beats.
But we haven’t even got started yet. But we have all of the elements needed to make a note.
Drooooopppp
var note = MIDINoteMessage(channel: 0,
note: 85,
velocity: 64,
releaseVelocity: 0,
duration: 5.0)
This note is all very nice, let us explain each part in turn:
- channel: The midi channel to be used
- note: The midi note (This is A5, musician friends)
- velocity: The force with which the note is played
- releaseVelocity: The speed with which that the key is released
- duration: The length of the note
We then create a rather attractively named MIDINoteEvent (which again returns an OSStatus), made up of the track (where the notes are played), time (time values in a sequence) and passing the note (that is the note to be played) by reference.
musicTrack = MusicTrackNewMIDINoteEvent(track, time, ¬e)
Now we have a MusicTrackNewMIDINoteEvent added, which is a music track containing a sequence and music track.
We can then create a MusicPlayer to, you know, play the track.
var musicPlayer : MusicPlayer? = nil
var player = NewMusicPlayer(&musicPlayer)
player = MusicPlayerSetSequence(musicPlayer!, sequence)
player = MusicPlayerStart(musicPlayer!)
The NewMusicPlayer
takes the MusicPlayer
passed by reference, which is then Set a sequence then actually started.
Awesome
The MIDI steps: multiple notes
Each note in turn will have a timestamp (obviously the next beat) and I can write each particular note as a UInt8
.
var notes: [UInt8] = [71,69,62,72,71,69,67]
To create multiple notes you can use MusicTrackNewMIDINoteEvent
multiple times. An example? Go on then: