How to create a volume meter in Swift 3

We’re going to create this easy volume meter with AVFoundation and a progress view

You can also add this to incoming audio(microphone), add audio effects, etc… but for now a player will do the job.

-First we start by adding a Progress view to the storyboard

Screen Shot 2017-03-18 at 3.05.36 PM.png


-Create an outlet in the Viewcontroller of the progress view and name it volumeMeter.

-Import AVFoundation

import AVFoundation


-include an audio File to your project navigator, you can use mine if you want

Click save link as…

-add these properties on the class  ViewController

    var engine: AVAudioEngine!      //The Audio Engine
    var player: AVAudioPlayerNode!  //The Player of the audiofile
    var file = AVAudioFile()        //Where we're going to store the audio file
    var timer: Timer?               //Timer to update the meter every 0.1 ms
    var volumeFloat:Float = 0.0     //Where We're going to store the volume float


-On the ViewDidLoad() add this code that will Initialize the engine and load the .m4a audio into an audiofile.

Note: replace the parameter strings for your audio file name “forResource” and “ofTYpe”.

        //init engine and player
        engine = AVAudioEngine()
        player = AVAudioPlayerNode()

        //Look for the audiofile on the project
        let path = Bundle.main.path(forResource: "Electronic", ofType: "m4a")!
        let url = NSURL.fileURL(withPath: path)

        //create the AVAudioFile
        let file = try? AVAudioFile(forReading: url)
        let buffer = AVAudioPCMBuffer(pcmFormat: file!.processingFormat, frameCapacity: AVAudioFrameCount(file!.length))
        do {
            //Do it
            try file!.read(into: buffer)
        } catch _ {


-Next we’re going to connect the player and the engine together

        engine.connect(player, to: engine.mainMixerNode, format: buffer.format)


-We’re going to installTap on the mainMixerNode to extract that juicy float

        //installTap with a bufferSize of 1024 with the processingFormat of the current audioFile on bus 0
        engine.mainMixerNode.installTap(onBus:0, bufferSize: 1024, format: file?.processingFormat) {
            (buffer : AVAudioPCMBuffer!, time : AVAudioTime!) in

            let dataptrptr = buffer.floatChannelData!           //Get buffer of floats
            let dataptr = dataptrptr.pointee
            let datum = dataptr[Int(buffer.frameLength) - 1]    //Get a single float to read

            //store the float on the variable
            self.volumeFloat = fabs((datum))



-Next we’re going to start the engine and play file.

        //Loop the audio file for demo purposes
        player.scheduleBuffer(buffer, at: nil, options: AVAudioPlayerNodeBufferOptions.loops, completionHandler: nil)

        do {
            try engine.start()
        } catch _ {


-Create a timer to update the volumeMeter every 0.1 miliseconds

        //start timer to update the meter
        timer = Timer.scheduledTimer(timeInterval: 0.1 , target: self, selector: #selector(updateMeter), userInfo: nil, repeats: true)


-Finally we’re going to create a new function named updateMeter() that’s going to be called by the timer every 0.1 ms to update the progressView with the new float value.

     func updateMeter() {
        self.volumeMeter.setProgress(volumeFloat, animated: false)

        if volumeMeter.progress > 0.8{//turn red if the volume is LOUD
            volumeMeter.tintColor =

        }else{//else green
            volumeMeter.tintColor =



This is not a perfect volume meter, but it’s a start for more advanced meters.

Github or die

volume meter 2

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s