Как добавить AVAudioUnitVarispeed в работающий AVAudioEngine, который воспроизводит несколько звуков одновременно - PullRequest
0 голосов
/ 14 марта 2020

Я создаю приложение для музыки c для 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?

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

1 Ответ

0 голосов
/ 08 мая 2020

Я тоже экспериментировал с этим. Прежде всего, я вижу, что вы пытаетесь подключить ваш audioFilePlayers к нескольким шинам AVAudioUnitVarispeed. Я не уверен, будет ли это работать, так как все разные speedControls будут получать разные входы и выход на другую шину. Шины предназначены для разделения сигнала источника на другой путь. Например, если вы хотите, чтобы ваш звук выводился на mainMixerNode, но также нужно сделать обход (ie, используя другую шину в качестве выхода), например, для эффекта Reverb, а затем на 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 из mainMixerNode.

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

...