title: How can audio files with different sampling rates be played at the correct pitch using AVFoundation?
body: <p>How should <code>AVAudioEngine</code> and <code>AVAudioPlayerNodes</code> be configured to play audio files at the correct pitch when they have different sampling rates?</p>
  <p>I read that the mixer can handle sampling rate conversion but I have not achieved this behaviour. I am using extensions to play looped audio segments and the players should function with compressed and PCM files; I don't know if that will dictate the solution.</p>
  <p>I attempted to use AVAudioConverter inside an <code>installTap(onBus:bufferSize:format: block:)</code> block on the <code>AVAudioPlayerNode</code> but have been getting various crashes and was unsure if this was the right solution. Was I on the right track or is a simpler solution available?</p>
  <pre><code>import AVFoundation
  import SwiftUI
  
  @main
  struct SampleRateMixerApp: App {
  
      private let audioEngine = AVAudioEngine()
      private let playerA = AVAudioPlayerNode()
      private let playerB = AVAudioPlayerNode()
  
      var body: some Scene {
          WindowGroup {
              Button(&quot;Play&quot;) { try? playFiles() }
              Button(&quot;Stop&quot;) { playerA.stop(); playerB.stop() }
          }
      }
  
      func playFiles() throws {
          _ = audioEngine.mainMixerNode
          try audioEngine.start()
          audioEngine.prepare()
  
          let pathA = Bundle.main.path(forResource: &quot;32khz_sample_rate&quot;, ofType: &quot;aif&quot;)!
          try setupPlayerNode(player: playerA, withAudioEngine: audioEngine, atPath: pathA)
  
          let pathB = Bundle.main.path(forResource: &quot;48khz_sample_rate&quot;, ofType: &quot;mp3&quot;)!
          try setupPlayerNode(player: playerB, withAudioEngine: audioEngine, atPath: pathB)
      }
  
      func setupPlayerNode(player: AVAudioPlayerNode, withAudioEngine engine: AVAudioEngine, atPath path: String) throws {
          engine.attach(player)
          engine.connect(player, to: engine.mainMixerNode, format: nil)
  
          let url = URL(string: path)!
          let file = try AVAudioFile(forReading: url)
          let frameCount = AVAudioFrameCount(file.length)
          let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: frameCount)!
          try file.read(into: buffer)
  
          player.scheduleBufferSegment(buffer, range: 0.25...0.75, looping: true)
          player.play()
      }
  }
  
  extension AVAudioPlayerNode {
  
      func scheduleBufferSegment(_ buffer: AVAudioPCMBuffer, range: ClosedRange&lt;Double&gt;, looping: Bool) {
          let length = Double(buffer.frameLength)
          let startFrame = AVAudioFramePosition(range.lowerBound * length)
          let endFrame = AVAudioFramePosition(range.upperBound * length)
  
          guard let bufferSegment = buffer.segment(from: startFrame, to: endFrame) else { return }
  
          if looping {
              scheduleBuffer(bufferSegment, at: nil, options: [.loops, .interrupts])
          } else {
              scheduleBuffer(bufferSegment, at: nil, options: [.interrupts]) {
                  DispatchQueue.main.async { [weak self] in
                      self?.stop()
                  }
              }
          }
      }
  }
  
  extension AVAudioPCMBuffer {
  
      func segment(from startFrame: AVAudioFramePosition, to endFrame: AVAudioFramePosition) -&gt; AVAudioPCMBuffer? {
          let framesToCopy = AVAudioFrameCount(endFrame - startFrame)
          guard let segment = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: framesToCopy) else { return nil }
  
          let sourcePointer = UnsafeMutableAudioBufferListPointer(mutableAudioBufferList)
          let destinationPointer = UnsafeMutableAudioBufferListPointer(segment.mutableAudioBufferList)
          let sampleSize = format.streamDescription.pointee.mBytesPerFrame
  
          for (source, destination) in zip(sourcePointer, destinationPointer) {
              memcpy(destination.mData,
                     source.mData?.advanced(by: Int(startFrame) * Int(sampleSize)),
                     Int(framesToCopy) * Int(sampleSize))
          }
  
          segment.frameLength = framesToCopy
          return segment
      }
  }
  </code></pre>