Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using multiple AKPlayers for multiple audio files playback

Tags:

swift

audiokit

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?

like image 475
Gavin Avatar asked Mar 06 '23 15:03

Gavin


1 Answers

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.

like image 135
dave234 Avatar answered May 16 '23 07:05

dave234