-
Notifications
You must be signed in to change notification settings - Fork 0
/
RealtimeAnalyzer.swift
187 lines (159 loc) · 7.65 KB
/
RealtimeAnalyzer.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//
// RealtimeAnalyzer.swift
// Pinna
///
// Created by Matt Chen on 2022/3/20.
//
import Foundation
import AVFoundation
import Accelerate
class RealtimeAnalyzer {
private var fftSize: Int
private lazy var fftSetup = vDSP_create_fftsetup(vDSP_Length(Int(round(log2(Double(fftSize))))), FFTRadix(kFFTRadix2))
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 var spectrumBuffer = [[Float]]()
public var spectrumSmooth: Float = 0.5 {
didSet {
spectrumSmooth = max(0.0, spectrumSmooth)
spectrumSmooth = min(1.0, spectrumSmooth)
}
}
init(fftSize: Int) {
self.fftSize = fftSize
}
deinit {
vDSP_destroy_fftsetup(fftSetup)
}
func analyse(with buffer: AVAudioPCMBuffer) -> [[Float]] {
let channelsAmplitudes = fft(buffer)
let aWeights = createFrequencyWeights()
if spectrumBuffer.count == 0 {
for _ in 0..<channelsAmplitudes.count {
spectrumBuffer.append(Array<Float>(repeating: 0, count: bands.count))
}
}
for (index, amplitudes) in channelsAmplitudes.enumerated() {
let weightedAmplitudes = 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)
let zipped = zip(spectrumBuffer[index], spectrum)
spectrumBuffer[index] = zipped.map { $0.0 * spectrumSmooth + $0.1 * (1 - spectrumSmooth) }
}
// print(channelsAmplitudes)
return spectrumBuffer
}
private func fft(_ buffer: AVAudioPCMBuffer) -> [[Float]] {
var amplitudes = [[Float]]()
guard let floatChannelData = buffer.floatChannelData else { return amplitudes }
//1:抽取buffer中的样本数据
var channels: UnsafePointer<UnsafeMutablePointer<Float>> = floatChannelData
let channelCount = Int(buffer.format.channelCount)
let isInterleaved = buffer.format.isInterleaved
if isInterleaved {
// deinterleave
let interleavedData = UnsafeBufferPointer(start: floatChannelData[0], count: self.fftSize * channelCount)
var channelsTemp : [UnsafeMutablePointer<Float>] = []
for i in 0..<channelCount {
var channelData = stride(from: i, to: interleavedData.count, by: channelCount).map{ interleavedData[$0] }
channelsTemp.append(UnsafeMutablePointer(&channelData))
}
channels = UnsafePointer(channelsTemp)
}
for i in 0..<channelCount {
let channel = channels[i]
//2: 加汉宁窗
var window = [Float](repeating: 0, count: Int(fftSize))
vDSP_hann_window(&window, vDSP_Length(fftSize), Int32(vDSP_HANN_NORM))
vDSP_vmul(channel, 1, window, 1, channel, 1, vDSP_Length(fftSize))
//3: 将实数包装成FFT要求的复数fftInOut,既是输入也是输出
var realp = [Float](repeating: 0.0, count: Int(fftSize / 2))
var imagp = [Float](repeating: 0.0, count: Int(fftSize / 2))
var fftInOut = DSPSplitComplex(realp: &realp, imagp: &imagp)
channel.withMemoryRebound(to: DSPComplex.self, capacity: fftSize) { (typeConvertedTransferBuffer) -> Void in
vDSP_ctoz(typeConvertedTransferBuffer, 2, &fftInOut, 1, vDSP_Length(fftSize / 2))
}
//4:执行FFT
vDSP_fft_zrip(fftSetup!, &fftInOut, 1, vDSP_Length(round(log2(Double(fftSize)))), FFTDirection(FFT_FORWARD));
//5:调整FFT结果,计算振幅
fftInOut.imagp[0] = 0
let fftNormFactor = Float(1.0 / (Float(fftSize)))
vDSP_vsmul(fftInOut.realp, 1, [fftNormFactor], fftInOut.realp, 1, vDSP_Length(fftSize / 2));
vDSP_vsmul(fftInOut.imagp, 1, [fftNormFactor], fftInOut.imagp, 1, vDSP_Length(fftSize / 2));
var channelAmplitudes = [Float](repeating: 0.0, count: Int(fftSize / 2))
vDSP_zvabs(&fftInOut, 1, &channelAmplitudes, 1, vDSP_Length(fftSize / 2));
channelAmplitudes[0] = channelAmplitudes[0] / 2 //直流分量的振幅需要再除以2
amplitudes.append(channelAmplitudes)
}
return amplitudes
}
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()!
}
public func findMajorFrequency(amplitudes: [Float]) -> Int{
var result: Int = 0;
var currentMax: Float = 0.0
for i in 0...amplitudes.count-1{
if amplitudes[i] > currentMax{
result = i;
currentMax = amplitudes[i]
}
}
return result
}
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
}
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
}
}