Как добавить AVAudioUnitVarispeed в рабочий AVAudioEngine, который одновременно воспроизводит несколько звуков

Я делаю музыкальное приложение для iOS. Я хочу воспроизводить 5 файлов одновременно. Это уже работает с помощью этого потока: Как одновременно воспроизводить несколько звуков из буфера с помощью узлов, подключенных к микшеру AVAudioEngine

Теперь мне нужно воспроизвести все файлы немного быстрее, используя AVAudioUnitVarispeed. Я не могу заставить это работать.

Вот код:

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)
        }
    }
}

Этот код воспроизводит 5 нот одновременно, если я заменю

// 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)

с участием

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

... и вызовите playSound ().

Но тогда у меня нет AVAudioUnitVarispeed (speedControl) ...

Итак, как добавить код для AVAudioUnitVarispeed?

Спасибо за помощь.


person Steffen Weber    schedule 14.03.2020    source источник


Ответы (1)


Я тоже экспериментировал с этим. Прежде всего я вижу, что вы пытаетесь подключить свой audioFilePlayers к нескольким шинам AVAudioUnitVarispeed. Я не уверен, что это сработает, поскольку все разные speedControls будут получать разные входы и выходы на другую шину. Шины предназначены для разделения сигнала источника на другой путь. Например, если вы хотите, чтобы звук выводился на узел mainMixerNode, но при этом выполнял обход (то есть используя другую шину в качестве вывода), например, к эффекту реверберации, а затем переходил к узлу mainMixerNode. Вы можете попробовать подключить все audioFilePlayers к шине 0 из speedControl, а затем подключить все speedControl к шине 0 ... i из .mainMixerNode. Посмотрим, работает ли это. В качестве альтернативы, если вы хотите, чтобы изменения скорости происходили на всех ваших дорожках, вы можете сделать speedControl мастер-эффект. так что вы бы сделали что-то вроде:

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)

Таким образом, вам понадобится только один экземпляр AVAudioUnitVarispeed, и вам не нужно будет изменять скорость для каждого игрока отдельно.

Или вы можете попробовать подключить все audioFilePlayers к шине 0 ... i одного speedControl и подключить speedControl к шине 0 ... i основногоMixerNode.

Надеюсь, что это работает для вас.

person patturik    schedule 08.05.2020