Crackling/Popping sound when using AVAudioUnitTimePitch

I have a simple AVAudioEngine graph as follows:

AVAudioPlayerNode -> AVAudioUnitEQ -> AVAudioUnitTimePitch -> AVAudioUnitReverb -> Main mixer node of AVAudioEngine.

I noticed that whenever I have AVAudioUnitTimePitch or AVAudioUnitVarispeed in the graph, I noticed a very distinct crackling/popping sound in my Airpods Pro 2 when starting up the engine and playing the AVAudioPlayerNode and unable to find the reason why this is happening. When I remove the node, the crackling completely goes away. How do I fix this problem since i need the user to be able to control the pitch and rate of the audio during playback.

import AVKit

@Observable @MainActor
class AudioEngineManager {
    nonisolated private let engine = AVAudioEngine()
    private let playerNode = AVAudioPlayerNode()
    private let reverb = AVAudioUnitReverb()
    private let pitch = AVAudioUnitTimePitch()
    private let eq = AVAudioUnitEQ(numberOfBands: 10)
    
    private var audioFile: AVAudioFile?
    
    private var fadePlayPauseTask: Task<Void, Error>?
    private var playPauseCurrentFadeTime: Double = 0
    
    init() {
        setupAudioEngine()
    }
    
    private func setupAudioEngine() {
        guard let url = Bundle.main.url(forResource: "Song name goes here", withExtension: "mp3") else {
            print("Audio file not found")
            return
        }
        
        do {
            audioFile = try AVAudioFile(forReading: url)
        } catch {
            print("Failed to load audio file: \(error)")
            return
        }
        
        reverb.loadFactoryPreset(.mediumHall)
        reverb.wetDryMix = 50
        
        pitch.pitch = 0 // Increase pitch by 500 cents (5 semitones)
        
        engine.attach(playerNode)
        engine.attach(pitch)
        engine.attach(reverb)
        engine.attach(eq)
        
        // Connect: player -> pitch -> reverb -> output
        engine.connect(playerNode, to: eq, format: audioFile?.processingFormat)
        engine.connect(eq, to: pitch, format: audioFile?.processingFormat)
        engine.connect(pitch, to: reverb, format: audioFile?.processingFormat)
        engine.connect(reverb, to: engine.mainMixerNode, format: audioFile?.processingFormat)
    }
    
    func prepare() {
        guard let audioFile else { return }
        playerNode.scheduleFile(audioFile, at: nil)
    }
    
    func play() {
        DispatchQueue.global().async { [weak self] in
            guard let self else { return }
            engine.prepare()
            try? engine.start()
                        
            DispatchQueue.main.async { [weak self] in
                guard let self else { return }
                
                playerNode.play()
                
                fadePlayPauseTask?.cancel()
                playPauseCurrentFadeTime = 0
                fadePlayPauseTask = Task { [weak self] in
                    guard let self else { return }
                    
                    while true {
                        let volume = updateVolume(for: playPauseCurrentFadeTime / 0.1, rising: true)
                        
                        // Ramp up volume until 1 is reached
                        if volume >= 1 { break }

                        engine.mainMixerNode.outputVolume = volume
                        try await Task.sleep(for: .milliseconds(10))
                        playPauseCurrentFadeTime += 0.01
                    }
                    
                    engine.mainMixerNode.outputVolume = 1
                }
            }
        }
    }
    
    func pause() {
        fadePlayPauseTask?.cancel()
        playPauseCurrentFadeTime = 0
        fadePlayPauseTask = Task { [weak self] in
            guard let self else { return }
            while true {
                let volume = updateVolume(for: playPauseCurrentFadeTime / 0.1, rising: false)
                
                // Ramp down volume until 0 is reached
                if volume <= 0 { break }

                engine.mainMixerNode.outputVolume = volume
                try await Task.sleep(for: .milliseconds(10))
                playPauseCurrentFadeTime += 0.01
            }
            
            engine.mainMixerNode.outputVolume = 0
            
            playerNode.pause()
            
            // Shut down engine once ramp down completes
            DispatchQueue.global().async { [weak self] in
                guard let self else { return }
                engine.pause()
            }
        }
    }
    
    private func updateVolume(for x: Double, rising: Bool) -> Float {
        if rising {
            // Fade in
            return Float(pow(x, 2) * (3.0 - 2.0 * (x)))
        } else {
            // Fade out
            return Float(1 - (pow(x, 2) * (3.0 - 2.0 * (x))))
        }
    }
    
    func setPitch(_ value: Float) {
        pitch.pitch = value
    }
    
    func setReverbMix(_ value: Float) {
        reverb.wetDryMix = value
    }
}

struct ContentView: View {
    @State private var audioManager = AudioEngineManager()
    @State private var pitch: Float = 0
    @State private var reverb: Float = 0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("🎵 Audio Player with Reverb & Pitch")
                .font(.title2)
            
            HStack {
                Button("Prepare") {
                    audioManager.prepare()
                }
                
                Button("Play") {
                    audioManager.play()
                }
                .padding()
                .background(Color.green)
                .foregroundColor(.white)
                .cornerRadius(10)
                
                Button("Pause") {
                    audioManager.pause()
                }
                .padding()
                .background(Color.red)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            VStack {
                Text("Pitch: \(Int(pitch)) cents")
                Slider(value: $pitch, in: -2400...2400, step: 100) { _ in
                    audioManager.setPitch(pitch)
                }
            }
            
            VStack {
                Text("Reverb Mix: \(Int(reverb))%")
                Slider(value: $reverb, in: 0...100, step: 1) { _ in
                    audioManager.setReverbMix(reverb)
                }
            }
        }
        .padding()
    }
}

Hi @Supernova01,

Thanks for reaching out.

Could you please file a bug report via Apple's Feedback Assistant using the link below? https://feedbackassistant.apple.com/new-form-response

When you submit it, please ensure you attach an Xcode project containing the relevant code and the specific audio file you're using.

Thanks

Crackling/Popping sound when using AVAudioUnitTimePitch
 
 
Q