Calculating Song Position in Music Apps with Swift and AVAudioEngine

Greg Cerveny
Aug 25, 2017 · 3 min read

Fundamentally, music is just sound organized in time. So when building musical tools, it’s helpful to indicate just where you are in time.

Familiar interfaces like the position indicator found in Logic solve this in the transport bar:

And if you happen to be using a MIDI sequencer like AVAudioSequencer you can access that value with handy properties like currentPositionInBeats: TimeInterval and currentPositionInSeconds: TimeInterval.

But how do you do this with a vanilla AVAudioEngine based application? Maybe you have an app that just wants to monitor recording time or augment a real time performance with beat information?

Capturing a Reference Time

You can only determine the current position if you have some some kind of reference time. Capturing this value is probably going to be associated with some other kind of event like starting playback or triggering an instrument for the first time.

It’s convenient to just grab a reference time from the AVAudioEngine’s default output node.

var engine = AVAudioEngine()
var offsetTime = engine.outputNode.lastRenderTime

Calculating Current Position in Seconds

Calculating the current position is really just a matter of taking the current time and subtracting the reference time. This makes a good candidate for a computed instance variable.

Here I’m using the difference in audio frames and dividing by the sample rate to get the duration seconds.

var currentPositionInSeconds: TimeInterval {
get {
let lastRenderTime = engine.outputNode.lastRenderTime
else { return 0 }
let frames = lastRenderTime.sampleTime -
offsetTime.sampleTime
return Double(frames) / offsetTime.sampleRate
}
}

Calculating Current Position in Beats

If you have tempo information, current position in beats is probably more helpful. With the beat format you can also track things like measures and divisions.

Utilizing this simple Tempo struct I previously wrote about, we can just divide currentPositionInSeconds by seconds in beat.

let myTempo = Tempo(bpm: 120.0)
var currentPositionInBeats: TimeInterval {
get { return currentPositionInSeconds / tempo.seconds() }
}

Displaying Beats

Much like AVAudioSequencer’s currentPositionInBeats this will output values like 1.125, 2.25, and many small positions in between.

To calculate measures or bars, first you need to know how many beats are in a measure. For 4/4 time, that would just be 4. And since you only want whole numbers, you round down.

let position = audioEngine.currentPositionInBeats
let bars = Int(floor(position / 4))

To display something like 1.2 to indicate the first measure, second beat, you just need to look at the remainders.

let bars = Int(floor(position / 4))
let beats = Int(floor(position.truncatingRemainder(dividingBy: 4)))
print("\(bars).\(beats)")

For more granularity you can just use additional divisions with values like 1/32, display the raw remainder, or display something else like ticks.

)

Greg Cerveny

Written by

Freelance Music Technology Developer. Connect with me: artfulmedium.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade