Using AudioKit's AVAudioPCMBuffer normalize function to normalize multiple audio files

286 Views Asked by At

I've got an array of audio files that I want to normalize so they all have similar perceived loudness. For testing purposes, I decided to adapt the AVAudioPCMBuffer.normalize method from AudioKit to suit my purposes. See here for implementation: https://github.com/AudioKit/AudioKit/blob/main/Sources/AudioKit/Audio%20Files/AVAudioPCMBuffer%2BProcessing.swift

I am converting each file into an AVAudioPCMBuffer, and then performing a reduce on that array of buffers to get the highest peak across all of the buffers. Then I created a new version of normalize called normalize(with peakAmplitude: Float) -> AVAudioPCMBuffer takes that peak amplitude, calculates a gainFactor and then iterates through the floatData for each channel and multiplies the floatData by the gainFactor. I then call my new flavor of normalize with the peak.amplitude that I get from the reduce operation on all the audio buffers.

This produces useful results, sometimes.

Here's the actual code in question:

extension AVAudioPCMBuffer {
      public func normalize(with peakAmplitude: Float) -> AVAudioPCMBuffer {
        guard let floatData = floatChannelData else { return self }
        
        let gainFactor: Float = 1 / peakAmplitude
        
        let length: AVAudioFrameCount = frameLength
        let channelCount = Int(format.channelCount)

        // i is the index in the buffer
        for i in 0 ..< Int(length) {
            // n is the channel
            for n in 0 ..< channelCount {
                let sample = floatData[n][i] * gainFactor
                self.floatChannelData?[n][i] = sample
            }
        }
        self.frameLength = length

        return self
    }
}

extension Array where Element == AVAudioPCMBuffer {
    public func normalized() -> [AVAudioPCMBuffer] {
        var minPeak = AVAudioPCMBuffer.Peak()
        minPeak.amplitude = AVAudioPCMBuffer.Peak.min
        
        let maxPeakForAllBuffers: AVAudioPCMBuffer.Peak = reduce(minPeak) { result, buffer in
            guard
                let currentBufferPeak = buffer.peak(),
                currentBufferPeak.amplitude > result.amplitude
            else {
                return result
            }
            
            return currentBufferPeak
        }
        
        return map { $0.normalize(with: maxPeakForAllBuffers.amplitude) }
    }
}

Three questions:

  1. Is my approach reasonable for multiple files?
  2. This appears to be using "peak normalization" vs RMS or EBU R128 normalization. Is that why when I give it a batch of 3 audio files and 2 of them are correctly made louder that 1 of them is made louder even though ffmpeg-normalize on the same batch of files makes that 1 file significantly quieter?
  3. Any other suggestions on ways to alter the floatData across multiple AVAudioAudioPCMBuffers in order to make them have similar perceived loudness?
0

There are 0 best solutions below