This post is a followup on the lesson 3b end exercise on the Udacity course, I discussed in my last post. Here’s the link:

https://www.udacity.com/course/ud585

We will also use this opportunity to trim down our final code.

Step1: Checking AVFoundation Framework

In iOS, all the audio visual media is contained within this framework. So it goes to reason that if there were any ways to change the rate, it should be contained here.

On searching through the framework, I found there were class to change the rate called AVAudioUnitTimePitch and AVAudioUnitVarispeed. Both the classes, have a property called rate which can be used for our purpose.

AVAudioUnitVarispeed is much closer in implementation to the AVAudioPlayer rate property but has a larger range – 0.25 to 4.0.

On the other hand AVAudioUnitTimePitch, not only has rate the range is much wider 1/32 (0.03125) to 32. It also has the pitch property which will be handy for doing other voice effects. Combining both the pitch and rate can lead to various others effects too.

Step2: Using AVAudioUnitTimePitch

If you were to google for an example code for AVAudioUnitTimePitch, you will end up on this stackoverflow page:

http://stackoverflow.com/questions/25333140/swift-using-sound-effects-with-audioengine

Now the example is about how to change the pitch of a sound:

//Audio Engine is initialized in viewDidLoad()
audioEngine = AVAudioEngine()

//The following Action is called on clicking a button
@IBAction func chipmunkPlayback(sender: UIButton) {
var pitchPlayer = AVAudioPlayerNode()
var timePitch = AVAudioUnitTimePitch()

timePitch.pitch = 1000
audioEngine.attachNode(pitchPlayer)
audioEngine.attachNode(timePitch)

audioEngine.connect(pitchPlayer, to: timePitch, format: myAudioFile.processingFormat)
audioEngine.connect(timePitch, to: audioEngine.outputNode, format: myAudioFile.processingFormat)

pitchPlayer.scheduleFile(myAudioFile, atTime: nil, completionHandler: nil)
audioEngine.startAndReturnError(&er)
pitchPlayer.play()
}

Notable part is the implementation of the pitch property. As rate is also a property, replacing “pitch” with “rate” should work. So my new code is:

var audioEngine: AVAudioEngine!
var audioFile: AVAudioFile!

override func viewDidLoad() {
super.viewDidLoad()

if var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3") {
var filePathURL = NSURL.fileURLWithPath(filePath)
audioEngine = AVAudioEngine()
audioFile = AVAudioFile(forReading: filePathUrl, error: nil)
}

@IBAction func playSoundSlowly(sender: UIButton) {

var audioPlayerNode = AVAudioPlayerNode()

audioPlayerNode.stop()
audioEngine.stop()
audioEngine.reset()

audioEngine.attachNode(audioPlayerNode)

var changeAudioUnitTime = AVAudioUnitTimePitch()

changeAudioUnitTime.rate = 0.5
audioEngine.attachNode(changeAudioUnitTime)
audioEngine.connect(audioPlayerNode, to: changeAudioUnitTime, format: nil)
audioEngine.connect(changeAudioUnitTime, to: audioEngine.outputNode, format: nil)
audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: nil)
audioEngine.startAndReturnError(nil)

audioPlayerNode.play()
}

Step3: Building a common function

We can now build a common function to play both the slow and fast sound effects form a single function:

var audioEngine: AVAudioEngine!
var audioFile: AVAudioFile!

override func viewDidLoad() {
super.viewDidLoad()

if var filePath = NSBundle.mainBundle().pathForResource("movie_quote", ofType: "mp3") {
var filePathURL = NSURL.fileURLWithPath(filePath)
audioEngine = AVAudioEngine()
audioFile = AVAudioFile(forReading: filePathUrl, error: nil)
}

@IBAction func playSoundSlowly(sender: UIButton) {
commonAudioFunction(0.5)
}
@IBAction func playSoundFast(sender: UIButton) {
commonAudioFunction(1.5)
}

func commonAudioFunction(audioChangeNumber: Float){
var audioPlayerNode = AVAudioPlayerNode()

audioPlayerNode.stop()
audioEngine.stop()
audioEngine.reset()

audioEngine.attachNode(audioPlayerNode)

var changeAudioUnitTime = AVAudioUnitTimePitch()

changeAudioUnitTime.rate = audioChangeNumber
audioEngine.attachNode(changeAudioUnitTime)
audioEngine.connect(audioPlayerNode, to: changeAudioUnitTime, format: nil)
audioEngine.connect(changeAudioUnitTime, to: audioEngine.outputNode, format: nil)
audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: nil)
audioEngine.startAndReturnError(nil)

audioPlayerNode.play()

}

Step 4: Robust end code

If you have completed the course, you will now have added the chipmunk and Darth Vader effect too. As both of those functions use the class, we can expand the common function to do more.

This also takes care of a bug which gets introduced due to using AVAudioPlayer and Engine separately:

var recievedAudio: RecordedAudio!
var audioEngine: AVAudioEngine!
var audioFile: AVAudioFile!

override func viewDidLoad() {
super.viewDidLoad()
audioEngine = AVAudioEngine()
audioFile = AVAudioFile(forReading: recievedAudio.filePathUrl, error: nil)
}

@IBAction func playSoundSlowly(sender: UIButton) {
commonAudioFunction(0.5, typeOfChange: "rate")

}

@IBAction func playSoundFast(sender: UIButton) {
commonAudioFunction(1.5, typeOfChange: "rate")
}

@IBAction func stopAudio(sender: UIButton) {
audioEngine.stop()
audioEngine.reset()

}

@IBAction func PlayChipmunkAction(sender: UIButton) {
commonAudioFunction(1000, typeOfChange: "pitch")
}

@IBAction func PlayDarthVaderAction(sender: UIButton) {
commonAudioFunction(-1000, typeOfChange: "pitch")
}

func commonAudioFunction(audioChangeNumber: Float, typeOfChange: String){
var audioPlayerNode = AVAudioPlayerNode()

audioPlayerNode.stop()
audioEngine.stop()
audioEngine.reset()

audioEngine.attachNode(audioPlayerNode)

var changeAudioUnitTime = AVAudioUnitTimePitch()

if (typeOfChange == "rate") {

changeAudioUnitTime.rate = audioChangeNumber

} else {
changeAudioUnitTime.pitch = audioChangeNumber
}
audioEngine.attachNode(changeAudioUnitTime)
audioEngine.connect(audioPlayerNode, to: changeAudioUnitTime, format: nil)
audioEngine.connect(changeAudioUnitTime, to: audioEngine.outputNode, format: nil)
audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: nil)
audioEngine.startAndReturnError(nil)

audioPlayerNode.play()

}

Advertisements