We can play a delayed copy of our sound with a smaller amplitude.
For reverb, we will simply repeat the process for the echoes.
In swift, an AVAudioPlayer instance has the playAtTime method, which allows us to schedule sound for future play. Exactly what we need for making echoes and reverbs!
The first thing we need to do is load an mp3 to check things are working. Then, we can add effect functions.
Since I am testing the code in an iPhone app, I will get the sound files and initialize the players in the viewDidLoad method, I will also hook up a button to call the playEcho method.
Here is the code:
class PlaySoundsViewController: UIViewController {
var audioPlayer:AVAudioPlayer!
var audioPlayer2:AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
let bundle = NSBundle.mainBundle()
let mp3Path = bundle.pathForResource("soundFileName",
ofType: "mp3")
var mp3URL = NSURL(fileURLWithPath: mp3Path!)
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL,
fileTypeHint: "mp3",
error: nil)
audioPlayer2 = AVAudioPlayer(contentsOfURL: mp3URL,
fileTypeHint: "mp3",
error: nil)
}
/*
hook this up to a button that will trigger
the echo in the UI
*/
@IBAction func playEcho(sender: UIButton) {
audioPlayer.stop()
audioPlayer.currentTime = 0;
audioPlayer.play()
let delay:NSTimeInterval = 0.1//100ms
var playtime:NSTimeInterval
playtime = audioPlayer2.deviceCurrentTime + delay
audioPlayer2.stop()
audioPlayer2.currentTime = 0
audioPlayer2.volume = 0.8;
audioPlayer2.playAtTime(playtime)
}
}
On to reverbs...
The procedure is almost the same, but now we have more players, different delays, and different volume levels. To deal with this, we can use an array of players. Each player will play its copy of the sound 20ms after the previous one starts.
[player0, player1, player2, ..., playerN] | | | | 0 20ms 40ms 20ms*N
We can use an exponential decay in the volume for each delayed signal. The decaying amplitude will make the later echoes less pronounced. This also reflects the exponential decay multiple echoes would have.
var reverbPlayers:[AVAudioPlayer] = []
let N:Int = 10
//initialize reverbPlayers in viewDidLoad
@IBAction func playReverb(sender: UIButton) {
/*
20ms produces detectable delays
*/
let delay:NSTimeInterval = 0.02
for i in 0...N {
var curDelay:NSTimeInterval = delay*NSTimeInterval(i)
var player:AVAudioPlayer = reverbPlayers[i]
//M_E is e=2.718...
//dividing N by 2 made it sound ok for the case N=10
var exponent:Double = -Double(i)/Double(N/2)
var volume = Float(pow(Double(M_E), exponent))
player.volume = volume
player.playAtTime(player.deviceCurrentTime + curDelay)
}
}
Full code:
import UIKit
import AVFoundation
class PlaySoundsViewController: UIViewController {
var audioPlayer:AVAudioPlayer!
var audioPlayer2:AVAudioPlayer!
var reverbPlayers:[AVAudioPlayer] = []
let N:Int = 10
override func viewDidLoad() {
super.viewDidLoad()
let bundle = NSBundle.mainBundle()
let mp3Path = bundle.pathForResource("fileName",
ofType: "mp3")
var mp3URL = NSURL(fileURLWithPath: mp3Path!)
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL,
fileTypeHint: "mp3",
error: nil)
audioPlayer2 = AVAudioPlayer(contentsOfURL: mp3URL,
fileTypeHint: "mp3",
error: nil)
for i in 0...N {
var temp = AVAudioPlayer(contentsOfURL: mp3URL,
fileTypeHint: "mp3",
error: nil)
reverbPlayers.append(temp)
}
}
@IBAction func playEcho(sender: UIButton) {
audioPlayer.stop()
audioPlayer.currentTime = 0;
audioPlayer.play()
let delay:NSTimeInterval = 0.1//100ms
var playtime:NSTimeInterval
playtime = audioPlayer2.deviceCurrentTime + delay
audioPlayer2.stop()
audioPlayer2.currentTime = 0
audioPlayer2.volume = 0.8;
audioPlayer2.playAtTime(playtime)
}
@IBAction func playReverb(sender: UIButton) {
/*
20ms produces detectable delays
*/
let delay:NSTimeInterval = 0.02
for i in 0...N {
var curDelay:NSTimeInterval = delay*NSTimeInterval(i)
var player:AVAudioPlayer = reverbPlayers[i]
//M_E is e=2.718...
//dividing N by 2 made it sound ok for the case N=10
var exponent:Double = -Double(i)/Double(N/2)
var volume = Float(pow(Double(M_E), exponent))
player.volume = volume
player.playAtTime(player.deviceCurrentTime + curDelay)
}
}
}
This is great. Thank you so much for writing this blog. My name is Kunal and I teach the iOS course you took at Udacity.
ReplyDeleteI love how you implemented echo. Here is another link that may help achieve reverb - https://developer.apple.com/library/prerelease/ios/documentation/AVFoundation/Reference/AVAudioUnitReverb_Class/index.html
Good stuff
ReplyDeleteAny way you could show how to do this in objective c?
ReplyDelete