# 一步一步教你实现iOS音频频谱动画（二）

·  阅读 10912

## 频带划分

``````public var frequencyBands: Int = 80 //频带数量
public var startFrequency: Float = 100 //起始频率
public var endFrequency: Float = 18000 //截止频率

``````private lazy var bands: [(lowerFrequency: Float, upperFrequency: Float)] = {
var bands = [(lowerFrequency: Float, upperFrequency: Float)]()
//1：根据起止频谱、频带数量确定增长的倍数：2^n
let n = log2(endFrequency/startFrequency) / Float(frequencyBands)
var nextBand: (lowerFrequency: Float, upperFrequency: Float) = (startFrequency, 0)
for i in 1...frequencyBands {
//2：频带的上频点是下频点的2^n倍
let highFrequency = nextBand.lowerFrequency * powf(2, n)
nextBand.upperFrequency = i == frequencyBands ? endFrequency : highFrequency
bands.append(nextBand)
nextBand.lowerFrequency = highFrequency
}
return bands
}()

``````private func findMaxAmplitude(for band:(lowerFrequency: Float, upperFrequency: Float), in amplitudes: [Float], with bandWidth: Float) -> Float {
let startIndex = Int(round(band.lowerFrequency / bandWidth))
let endIndex = min(Int(round(band.upperFrequency / bandWidth)), amplitudes.count - 1)
return amplitudes[startIndex...endIndex].max()!
}

``````func analyse(with buffer: AVAudioPCMBuffer) -> [[Float]] {
let channelsAmplitudes = fft(buffer)
var spectra = [[Float]]()
for amplitudes in channelsAmplitudes {
let spectrum = bands.map {
findMaxAmplitude(for: \$0, in: amplitudes, with: Float(buffer.format.sampleRate)  / Float(self.fftSize))
}
spectra.append(spectrum)
}
return spectra
}

## 动画绘制

``````var leftGradientLayer = CAGradientLayer()

``````private func setupView() {
rightGradientLayer.colors = [UIColor.init(red: 52/255, green: 232/255, blue: 158/255, alpha: 1.0).cgColor,
UIColor.init(red: 15/255, green: 52/255, blue: 67/255, alpha: 1.0).cgColor]

leftGradientLayer.colors = [UIColor.init(red: 194/255, green: 21/255, blue: 0/255, alpha: 1.0).cgColor,
UIColor.init(red: 255/255, green: 197/255, blue: 0/255, alpha: 1.0).cgColor]
}

``````override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
setupView()
}

``````var spectra:[[Float]]? {
didSet {
if let spectra = spectra {
// left channel
let leftPath = UIBezierPath()
for (i, amplitude) in spectra[0].enumerated() {
let x = CGFloat(i) * (barWidth + space) + space
let y = translateAmplitudeToYPosition(amplitude: amplitude)
let bar = UIBezierPath(rect: CGRect(x: x, y: y, width: barWidth, height: bounds.height - bottomSpace - y))
leftPath.append(bar)
}
leftGradientLayer.frame = CGRect(x: 0, y: topSpace, width: bounds.width, height: bounds.height - topSpace - bottomSpace)

// right channel
if spectra.count >= 2 {
let rightPath = UIBezierPath()
for (i, amplitude) in spectra[1].enumerated() {
let x = CGFloat(spectra[1].count - 1 - i) * (barWidth + space) + space
let y = translateAmplitudeToYPosition(amplitude: amplitude)
let bar = UIBezierPath(rect: CGRect(x: x, y: y, width: barWidth, height: bounds.height - bottomSpace - y))
rightPath.append(bar)
}
rightGradientLayer.frame = CGRect(x: 0, y: topSpace, width: bounds.width, height: bounds.height - topSpace - bottomSpace)
}
}
}
}

``````private func translateAmplitudeToYPosition(amplitude: Float) -> CGFloat {
let barHeight: CGFloat = CGFloat(amplitude) * (bounds.height - bottomSpace - topSpace)
return bounds.height - bottomSpace - barHeight
}

``````// MARK: SpectrumPlayerDelegate
extension ViewController: AudioSpectrumPlayerDelegate {
func player(_ player: AudioSpectrumPlayer, didGenerateSpectrum spectra: [[Float]]) {
DispatchQueue.main.async {
//1: 将数据交给spectrumView
self.spectrumView.spectra = spectra
}
}
}

## 调整优化

### 节奏匹配

``````func analyse(with buffer: AVAudioPCMBuffer) -> [[Float]] {
let channelsAmplitudes = fft(buffer)
var spectra = [[Float]]()
for amplitudes in channelsAmplitudes {
let spectrum = bands.map {
//1: 直接在此函数调用后乘以5
findMaxAmplitude(for: \$0, in: amplitudes, with: Float(buffer.format.sampleRate)  / Float(self.fftSize)) * 5
}
spectra.append(spectrum)
}
return spectra
}

`RealtimeAnalyzer`类中新建函数`createFrequencyWeights()`，它将返回A计权的系数数组：

``````private func createFrequencyWeights() -> [Float] {
let Δf = 44100.0 / Float(fftSize)
let bins = fftSize / 2 //返回数组的大小
var f = (0..<bins).map { Float(\$0) * Δf}
f = f.map { \$0 * \$0 }

let c1 = powf(12194.217, 2.0)
let c2 = powf(20.598997, 2.0)
let c3 = powf(107.65265, 2.0)
let c4 = powf(737.86223, 2.0)

let num = f.map { c1 * \$0 * \$0 }
let den = f.map { (\$0 + c2) * sqrtf((\$0 + c3) * (\$0 + c4)) * (\$0 + c1) }
let weights = num.enumerated().map { (index, ele) in
return 1.2589 * ele / den[index]
}
return weights
}

``````func analyse(with buffer: AVAudioPCMBuffer) -> [[Float]] {
let channelsAmplitudes = fft(buffer)
var spectra = [[Float]]()
//1: 创建权重数组
let aWeights = createFrequencyWeights()
for amplitudes in channelsAmplitudes {
//2：原始频谱数据依次与权重相乘
let weightedAmplitudes = amplitudes.enumerated().map {(index, element) in
return element * aWeights[index]
}
let spectrum = bands.map {
//3: findMaxAmplitude函数将从新的`weightedAmplitudes`中查找最大值
findMaxAmplitude(for: \$0, in: weightedAmplitudes, with: Float(buffer.format.sampleRate)  / Float(self.fftSize)) * 5
}
spectra.append(spectrum)
}
return spectra
}

### 锯齿消除

``````private func highlightWaveform(spectrum: [Float]) -> [Float] {
//1: 定义权重数组，数组中间的5表示自己的权重
//   可以随意修改，个数需要奇数
let weights: [Float] = [1, 2, 3, 5, 3, 2, 1]
let totalWeights = Float(weights.reduce(0, +))
let startIndex = weights.count / 2
//2: 开头几个不参与计算
var averagedSpectrum = Array(spectrum[0..<startIndex])
for i in startIndex..<spectrum.count - startIndex {
//3: zip作用: zip([a,b,c], [x,y,z]) -> [(a,x), (b,y), (c,z)]
let zipped = zip(Array(spectrum[i - startIndex...i + startIndex]), weights)
let averaged = zipped.map { \$0.0 * \$0.1 }.reduce(0, +) / totalWeights
averagedSpectrum.append(averaged)
}
//4：末尾几个不参与计算
averagedSpectrum.append(contentsOf: Array(spectrum.suffix(startIndex)))
return averagedSpectrum
}

`analyse`函数需要再次更新：

``````func analyse(with buffer: AVAudioPCMBuffer) -> [[Float]] {
let channelsAmplitudes = fft(buffer)
var spectra = [[Float]]()
for amplitudes in channelsAmplitudes {
let weightedAmplitudes = amplitudes.enumerated().map {(index, element) in
return element * weights[index]
}
let spectrum = bands.map {
findMaxAmplitude(for: \$0, in: weightedAmplitudes, with: Float(buffer.format.sampleRate)  / Float(self.fftSize)) * 5
}
//1: 添加到数组之前调用highlightWaveform
spectra.append(highlightWaveform(spectrum: spectrum))
}
return spectra
}

### 闪动优化

``````//缓存上一帧的值
private var spectrumBuffer: [[Float]]?
//缓动系数，数值越大动画越"缓"
public var spectrumSmooth: Float = 0.5 {
didSet {
spectrumSmooth = max(0.0, spectrumSmooth)
spectrumSmooth = min(1.0, spectrumSmooth)
}
}

``````func analyse(with buffer: AVAudioPCMBuffer) -> [[Float]] {
let channelsAmplitudes = fft(buffer)
let aWeights = createFrequencyWeights()
//1: 初始化spectrumBuffer
if spectrumBuffer.count == 0 {
for _ in 0..<channelsAmplitudes.count {
spectrumBuffer.append(Array<Float>(repeating: 0, count: frequencyBands))
}
}
//2: index在给spectrumBuffer赋值时需要用到
for (index, amplitudes) in channelsAmplitudes.enumerated() {
let weightedAmp = amplitudes.enumerated().map {(index, element) in
return element * aWeights[index]
}
var spectrum = bands.map {
findMaxAmplitude(for: \$0, in: weightedAmplitudes, with: Float(buffer.format.sampleRate)  / Float(self.fftSize)) * 5
}
spectrum = highlightWaveform(spectrum: spectrum)
//3: zip用法前面已经介绍过了
let zipped = zip(spectrumBuffer[index], spectrum)
spectrumBuffer[index] = zipped.map { \$0.0 * spectrumSmooth + \$0.1 * (1 - spectrumSmooth) }
}
return spectrumBuffer
}

## 结尾

[1] 维基百科, 倍频程频带, en.wikipedia.org/wiki/Octave…
[2] 维基百科, 响度, zh.wikipedia.org/wiki/%E9%9F…
[3] mathworks，A-weighting Filter with Matlab，www.mathworks.com/matlabcentr…
[4] 动画效果：网易云音乐APPMOO音乐APP。感兴趣的同学可以用`卡农钢琴版`音乐和这两款APP进行对比^_^，会发现区别。

iOS