Dynamically reconnecting AVAudioEngine nodes without stopping sound

I'm trying to dynamically modify a graph of nodes in attached to an AVAudioEngine. The graph correctly outputs sound but then outputs silence as soon as I call connect(_:to:format:) .


In the below sample, I'd like to dynamically connect player to engine.mainMixerNode, but whenever I call toggleBypass I get silence.


Is it possible to carry out this rewiring without pausing playback of the AVAudioPlayerNode?


class Sample: UIViewController {

    let engine = AVAudioEngine()
    let player = AVAudioPlayerNode()
    let effectNode = AVAudioUnitDelay()

    @objc func toggleBypass() {
        if effectNode.numberOfInputs == 0 {
            engine.connect(player, to: effectNode, format: file.processingFormat)
            engine.connect(effectNode, to: engine.mainMixerNode, format: file.processingFormat)
        } else {
            engine.connect(player, to: engine.mainMixerNode, format: file.processingFormat)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(toggleBypass)))

        try! AVAudioSession.sharedInstance().setCategory(.playback)
        try! AVAudioSession.sharedInstance().setPreferredIOBufferDuration(0.005)
        try! AVAudioSession.sharedInstance().setActive(true, options: [])

        do {
            engine.attach(player)
            engine.attach(effectNode)
            engine.connect(player, to: effectNode, format: file.processingFormat)
            engine.connect(effectNode, to: engine.mainMixerNode, format: file.processingFormat)

            player.scheduleBuffer(buffer, at: nil, options: .loops, completionHandler: nil)

            engine.prepare()
            try engine.start()

            player.play()
        } catch {
            assertionFailure(String(describing: error))
        }
    }


    lazy var file: AVAudioFile = {
        let fileURL = Bundle.main.url(forResource: "filename", withExtension: "mp3")!
        return try! AVAudioFile(forReading: fileURL)
    }()


    lazy var buffer: AVAudioPCMBuffer = {
        let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: UInt32(file.length))!
        try! file.read(into: buffer)
        return buffer
    }()
}

Replies

I think you can achieve the same by using the bypass property of AVAudioUnitDelay (https://developer.apple.com/documentation/avfoundation/avaudiouniteffect/1386894-bypass).

I have enough experience to say that you could disconnect/reconnect node inputs outputs in the past using `AUGraph methods, so it should generally work. But I do not have enough experience to claim that it should work in AVAudioEngine` as well. It may be buggy.