What’s really cool about Audio Kit is that MIDI is related to electronic music,

This article briefly looks at the Audio Kit playback related source code

Call part

let engine = AudioEngine() let player = AudioPlayer() override func viewDidLoad() { super.viewDidLoad() player.isLooping  = true engine.output = player do { try engine.start() } catch { print("AudioKit did not start! \(error)")}} // stop playing func toStop(){player.stop()} // Play func toPlay(){player.stop() let url = Bundle.main.url(forResource: "x", withExtension: "mp3") var file: AVAudioFile? do { file = try AVAudioFile(forReading: url!) } catch { print(error) } guard let f = file else { return } let buffer = try! AVAudioPCMBuffer(file: f)! player.buffer = buffer player.schedule(at: nil) player.play() }Copy the code

Play using 3 steps:

  • Create engine and Player and specify the outputengine.output = player

Open the engine again

  • Preparing to play the file

Take the url of the file, create AVAudioFile,

I’m going to take AVAudioFile, I’m going to create AVAudioPCMBuffer,

  • Schedule AVAudioPCMBuffer for audio player node to play

Source code

Prepare the engine to work, connect the nodes,

Engine. Output = player

/// Output node public var output: Node? { didSet { // AVAudioEngine doesn't allow the outputNode to be changed while the engine is running let wasRunning = avEngine.isRunning if wasRunning { stop() } // remove the exisiting node if it is present if let node = oldValue { mainMixerNode? .removeInput(node) node.detach() avEngine.outputNode.disconnect(input: node.avAudioNode) } // if non nil, set the main output now if let node = output { avEngine.attach(node.avAudioNode) // has the sample rate changed? if let currentSampleRate = mainMixerNode?.avAudioUnitOrNode.outputFormat(forBus: 0).sampleRate, currentSampleRate ! = Settings.sampleRate { print("Sample Rate has changed, creating new mainMixerNode at", Settings.samplerate) removeEngineMixer()} Create the on demand Mixer if needed createEngineMixer() mainMixerNode? .addInput(node) mainMixerNode? .makeAVConnections() } if wasRunning { try? start() } } }Copy the code

Inside the code, mainly is to ring in the New Year

Deprecated: this engine, probably not the first time used, should be removed from its previous state

Orientation: Establish the state we need

Add Player, Mixer, and avEngine. OutputNode to Engine.

engine: player -> mixer -> avEngine.outputNode

   private func createEngineMixer() {
        guard mainMixerNode == nil else { return }

        let mixer = Mixer()
        avEngine.attach(mixer.avAudioNode)
        avEngine.connect(mixer.avAudioNode, to: avEngine.outputNode, format: Settings.audioFormat)
        mainMixerNode = mixer
    }
Copy the code

In this method, avEngine adds and connects the Mixer nodes

/// Add input to the mixer /// - Parameter node: Node to add public func addInput(_ node: Node) { guard ! HasInput (node) else {print("🛑 Error: Node is already connected to Mixer.") return } connections.append(node) makeAVConnections() }Copy the code

As you can see, the logic here is a little redundant,

If mixer had this node, makeAVConnections() would just go once,

Otherwise, it is generally called one more time

mainMixerNode? .addInput(node) mainMixerNode? .makeAVConnections()Copy the code

Preparing audio Files

extension AVAudioPCMBuffer { /// Read the contents of the url into this buffer public convenience init? (url: URL) throws { guard let file = try? AVAudioFile(forReading: url) else { return nil } try self.init(file: file) } /// Read entire file and return a new AVAudioPCMBuffer with its contents public convenience init? (file: AVAudioFile) throws { file.framePosition = 0 self.init(pcmFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length)) try file.read(into: self) } }Copy the code

The key is this method, public convenience init? (file: AVAudioFile) throws

We need to get a block of memory,

And read in the audio data

Go to play

That is, give resources, schedule resources to play

Player.schedule (at: nil) schedules audio resources to the player node.

public func schedule(at when: AVAudioTime? = nil) { if isBuffered, let buffer = buffer { playerNode.scheduleBuffer(buffer, at: nil, options: bufferOptions, completionCallbackType: .dataPlayedBack) { _ in self.internalCompletionHandler() } scheduleTime = when ?? AVAudioTime.now() } else if let file =  file { playerNode.scheduleFile(file, at: when, completionCallbackType: .dataPlayedBack) { _ in self.internalCompletionHandler() } scheduleTime = when ?? AVAudioTime.now() } else { print("The player needs a file or a valid buffer to schedule") scheduleTime = nil } }Copy the code

There are two types of playback, audio buffer and audio file

With audio playback files, it is good to play

Other features:

Loop play function

Change the attributes of the record first

  public var isLooping: Bool = false {
      didSet {
          bufferOptions = isLooping ? .loops : .interrupts
      }
  }
Copy the code

Then there’s the timing of the loop

Scheduling audio resource methods that have a completion callback

open func scheduleBuffer(_ buffer: AVAudioPCMBuffer, at when: AVAudioTime? , options: AVAudioPlayerNodeBufferOptions = [], completionCallbackType callbackType: AVAudioPlayerNodeCompletionCallbackType, completionHandler: AVAudioPlayerNodeCompletionHandler? = nil)Copy the code

In its callback method,

If loop is required, play again, go play()

func internalCompletionHandler() { guard isPlaying, engine? .isInManualRenderingMode == false else { return } scheduleTime = nil completionHandler? () isPlaying = false if ! isBuffered, isLooping, engine? .isRunning == true { print("Playing loop..." ) play() return } }Copy the code

The audio time

extension AVAudioFile {
    /// Duration in seconds
    public var duration: TimeInterval {
        Double(length) / fileFormat.sampleRate
    }
}

Copy the code

Audio resource file, how many frames are in it, just look at length,

Get the number of all frames for the first time

The total number of frames/sample rate is the length of the audio file

github repo