I am trying to play 4 mp3 files synchronously using AudioKit's AKPlayer, and it was pretty successful. As a part of my Swift study, though, I wanted to simplify my code using arrays (or something I haven't learned yet) since I feel there's something redundant in my code, simply copying player codes for four times. Below is the code I've written:
let file1 = try? AKAudioFile(readFileName: "mixing_1_vocal.mp3")
let file2 = try? AKAudioFile(readFileName: "mixing_2_drums.mp3")
let file3 = try? AKAudioFile(readFileName: "mixing_3_synth.mp3")
let file4 = try? AKAudioFile(readFileName: "mixing_4_bass.mp3")
let player1 = AKPlayer(audioFile: file1!)
let player2 = AKPlayer(audioFile: file2!)
let player3 = AKPlayer(audioFile: file3!)
let player4 = AKPlayer(audioFile: file4!)
let startTime = AVAudioTime.now() + 0.25
let mixer = AKMixer()
player1 >>> mixer
player2 >>> mixer
player3 >>> mixer
player4 >>> mixer
player1.isLooping = true
player1.buffering = .always
player2.isLooping = true
player2.buffering = .always
player3.isLooping = true
player3.buffering = .always
player4.isLooping = true
player4.buffering = .always
AudioKit.output = mixer
try? AudioKit.start()
player1.start(at: startTime)
player2.start(at: startTime)
player3.start(at: startTime)
player4.start(at: startTime)
This is it! There are four different tracks (individual instruments), and they are supposed to be played at the same time, looped infinitely. It will be very helpful if anybody could help me improve my code. Are there better ways to perform the same work?
This is a pretty open ended question, but here's an example ViewController for iOS.
class ViewController: UIViewController {
let mixer = AKMixer()
let players: [AKPlayer] = {
do {
let filenames = ["mixing_1_vocal.mp3",
"mixing_2_drums.mp3",
"mixing_3_synth.mp3",
"mixing_4_bass.mp3"]
return try filenames.map { AKPlayer(audioFile: try AKAudioFile(readFileName: $0)) }
} catch {
fatalError()
}
}()
override func viewDidLoad() {
super.viewDidLoad()
makeConnections()
startAudioEngine()
preparePlayers()
startPlayers()
}
func makeConnections() {
players.forEach { $0 >>> mixer }
AudioKit.output = mixer
}
func startAudioEngine() {
do {
try AudioKit.start()
} catch {
print(error)
fatalError()
}
}
func preparePlayers() {
players.forEach { player in
player.isLooping = true
player.buffering = .always
player.prepare()
}
}
func startPlayers() {
let startTime = AVAudioTime.now() + 0.25
players.forEach { $0.start(at: startTime) }
}
}
Key points:
Use do {try expression} catch { error }
, not try? expression
. While convenient to ignore an error, it will quickly bite you down the road.
Map and forEach, map is crucial for working with arrays. Notice how an array of filenames is transformed into an array of AKPlayers. Related, shorthand argument names: players.forEach { player in player.play() }
is the same as players.forEach { $0.play() }
. Also related, map re-throws, so you can try inside of map's closure.
Use closures to encapsulate. The players variable is populated as it's defined using an immediately evaluated closure. The alternative is to have to create this array on init, then later populate it.
Finally, separate logic into functions as much as possible. It makes your code more readable, and easier to maintain.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With