0

I´m making a music app for iOS. I want to play 5 files simultaneously. This already works with help of this thread: How to play multiple sounds from buffer simultaneously using nodes connected to AVAudioEngine's mixer

Now I need to play all the files a little faster using AVAudioUnitVarispeed. I cannot get this to work.

Here is the code:

import AVFoundation

class Audio {

    // MARK: AUDIO VARIABLEN
    var engineFirst: AVAudioEngine = AVAudioEngine()
    var audioFilePlayer = [AVAudioPlayerNode]()
    var noteFilePath = [String]()

    // Speed
    var speedControl = [AVAudioUnitVarispeed]()

    var noteFileURL = [URL]()
    var noteAudioFile = [AVAudioFile]()
    var noteAudioFileBuffer = [AVAudioPCMBuffer]()
    let tonAnzahl = 5
    let latency = 0.03
    var playing = false

    // MARK: PLAY SOUND
    func playSound() {
        playing = true
        for i in 0...tonAnzahl - 1 {
            audioFilePlayer[i].scheduleBuffer(noteAudioFileBuffer[i], at: nil, completionHandler: nil)
            audioFilePlayer[i].play()
        }

    }

    // MARK: STOP SOUND
    func stopSound() {
        playing = false
        for i in 0...tonAnzahl - 1 {
            audioFilePlayer[i].volume = 0
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + latency, execute: {
            for i in 0...self.tonAnzahl - 1 {
                self.audioFilePlayer[i].stop()
            }
        })
    }

    // MARK: SETUP AUDIO ENGINE
    func setupAudioEngine(bassFile: String, terzFile: String, septimeFile: String, tensionOneFile: String, tensionTwoFile: String) {
        do {
            noteAudioFile.removeAll()
            noteFileURL.removeAll()
            // For each note, read the note URL into an AVAudioFile,
            // setup the AVAudioPCMBuffer using data read from the file,
            // and read the AVAudioFile into the corresponding buffer
            for i in 0...tonAnzahl - 1 {
                noteFilePath = [
                    Bundle.main.path(forResource: bassFile, ofType: "mp3")!,
                    Bundle.main.path(forResource: terzFile, ofType: "mp3")!,
                    Bundle.main.path(forResource: septimeFile, ofType: "mp3")!,
                    Bundle.main.path(forResource: tensionOneFile, ofType: "mp3")!,
                    Bundle.main.path(forResource: tensionTwoFile, ofType: "mp3")!]
                noteFileURL.append(URL(fileURLWithPath: noteFilePath[i]))

                try noteAudioFile.append(AVAudioFile(forReading: noteFileURL[i]))

                let noteAudioFormat = noteAudioFile[i].processingFormat
                let noteAudioFrameCount = UInt32(noteAudioFile[i].length)
                noteAudioFileBuffer.append(AVAudioPCMBuffer(pcmFormat: noteAudioFormat, frameCapacity: noteAudioFrameCount)!)

                try noteAudioFile[i].read(into: noteAudioFileBuffer[i])
            }
            // For each note, attach the corresponding node to the engineFirst, and connect the node to the engineFirst's mixer.
            for i in 0...tonAnzahl - 1 {
                audioFilePlayer.append(AVAudioPlayerNode())
                engineFirst.attach(audioFilePlayer[i])


                // HERE IS THE PROBLEM
                speedControl.append(AVAudioUnitVarispeed())
                speedControl[i].rate = 2
                engineFirst.attach(speedControl[i])

                engineFirst.connect(audioFilePlayer[i], to: speedControl[i], fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)
                engineFirst.connect(speedControl[i], to: engineFirst.mainMixerNode, fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)

            }

            // Audio Engine Start
            try engineFirst.start()

            // Setup the audio session to play sound in the app, and activate the audio session
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient)
            try AVAudioSession.sharedInstance().setMode(AVAudioSession.Mode.default)
            try AVAudioSession.sharedInstance().setActive(true)
        }
        catch let error {
            print(error.localizedDescription)
        }
    }
}

This code plays 5 notes simultaneously if I replace

// HERE IS THE PROBLEM
speedControl.append(AVAudioUnitVarispeed())
speedControl[i].rate = 2
engineFirst.attach(speedControl[i])

engineFirst.connect(audioFilePlayer[i], to: speedControl[i], fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)
engineFirst.connect(speedControl[i], to: engineFirst.mainMixerNode, fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)

with

engineFirst.connect(audioFilePlayer[i], to: engineFirst.mainMixerNode, fromBus: 0, toBus: i, format: noteAudioFileBuffer[i].format)

... and call playSound().

But then I don't have the AVAudioUnitVarispeed (speedControl) implemented...

So, how to add the code for AVAudioUnitVarispeed?

Thank you for your help.

1 Answers1

0

I've been experimenting with this also. First of all I see you are trying to connect your audioFilePlayers to several busses of the AVAudioUnitVarispeed. I'm not sure if this will work since all the different speedControls will get different inputs and output to a different bus. Busses are meant to split the Source signal to a different path. For example if you want your audio to output to the mainMixerNode, but also make a detour (ie using another bus as output) to for example a Reverb effect and then on to the mainMixerNode. You can try to connect all the audioFilePlayers to bus 0 of the speedControl, and then connect all speedControl to bus 0...i of the .mainMixerNode. See if that works. Alternatively, if you want the speed changes to occur on all your tracks, you can make speedControl a master effect. so you would do something like :

engineFirst.connect(audioFilePlayer[i], to: engineFirst.mainMixerNode, format: noteAudioFileBuffer[i].format)  

// And then put this outside of the loop to connect all audioFilePlayers

engineFirst.connect(engineFirst.mainMixerNode, to: speedControl, format: noteAudioFileBuffer[0].format)
engineFirst.connect(speedcontrol, to: engineFirst.outputNode, format: noteAudioFileBuffer[0].format)

This way you will only need one instance of AVAudioUnitVarispeed and you don't have to change the speed for each player seperately.

Or you could try to connect all the audioFilePlayers to bus 0...i of a single speedControl and connect speedControl to bus 0...i of the mainMixerNode.

Hope this works for you.

patturik
  • 135
  • 1
  • 8