XcodeでFFT(vDSP_fft_zrip)を使用するとFloat.infinityが出てしまう
Mac/High Sierra 10.13.6/swift4.2/xcode 10.0
Xcodeのシミュレータから音を取得しFFTをかけると
Xcodeで結果にFloat.infinityが現れる
取得した音をファイル出力し、playground側で
読み込んでFFTをかけるとFloat.infinityが出ず、
結果が異なってしまうことに困っています。
音の取得部分は継続的に音を取得したいため
AQAudioQueueInputCallbackを利用し、
バッファに結果を格納しています。
参考サイト
上記サイトと異なるところは上記サイトではInt8で音データをバッファから取得していますが、私はInt16で取り出しています。音のデータ形式が16ビットなので。
バッファにある程度溜まった段階で音波データを取得しFFTをかけるという処理をTimerでぐるぐる回しています。この時にFloat.infinityが出ることが確認できています。
一方で、この時同時に波形データをcsvで書き出しているのですが、この結果を使うとFloat.infinityが出ません。また、FFTで得られた結果はFloat.infinityが出ること以外はよく一致することも確認しています。
両者の違いとしてはTimerでくるくる回していることでしょうか。
ただ、毎回録音するためのオブジェクトはインスタンス化し直しているため、別のバッファを参照し、書き込んでいる間に読み出すと言ったような処理を行っていることも考えにくいです。
どうしてFloat.infinityが出てしまうのでしょうか。
FFTは下記のfftExecute(ntimes n:Int)で比較しております。ntimesが1でも2でも4でも結果は同様でした。
Float.infinityを0として扱い、両者のピーク構造を比較したところFloat.infinity以外の部分ではよく一致していました。
import Accelerate
class FFT {
var delta:Float = 0
var dataarray:[Float]?
var datalength:Int?
var rate:Float?
var strength = [String:Float]()
var frequency = [String:Float]()
var strengthArray = [Float]()
var frequencyArray = [Float]()
init(_ vector:[Float],rate:Float){
datalength = vector.count
dataarray = vector
self.rate = rate
}
init(_ vector:[Int8],rate:Float){
datalength = vector.count
dataarray = vector.compactMap({return Float($0)/Float(Int8.max)})
self.rate = rate
}
init(_ vector:[Int16],rate:Float){
datalength = vector.count
dataarray = vector.compactMap({return Float($0)/Float(Int16.max)})
self.rate = rate
}
func makeComplexVector(arr:inout [Float]) -> DSPSplitComplex {
var empty:[Float] = Array<Float>(repeating:0.0,count:arr.count)
let cvector=DSPSplitComplex(realp:&arr,imagp:&empty)
return cvector
}
func applyWindowFunction(){
// 窓関数
//dataarrayに窓関数を適用してdataarrayに再格納
var windowData = [Float](repeating:0,count: datalength!)
var windowOutput = [Float](repeating:0,count: datalength!)
vDSP_hann_window(&windowData, vDSP_Length(datalength!), Int32(0))
vDSP_vmul(&dataarray!, 1, &windowData, 1, &windowOutput, 1, vDSP_Length(datalength!))
dataarray = windowOutput
}
func createStrengthArray(){
strengthArray = []
for i in 0..<strength.count{
strengthArray.append(strength[String(i)]!)
}
}
func createFrequencyArray(){
frequencyArray = []
for i in 0..<frequency.count{
frequencyArray.append(frequency[String(i)]!)
}
}
func fftExecute(ntimes n:Int){
let start = Date()//時間計測
//窓関数の適用
applyWindowFunction()
if n > 1{addZeroArray(n)}
delta = Float(dataarray!.count)/rate!
let length = UInt(log2(Double(dataarray!.count*2)))
let radix:FFTRadix = 2
let set_up = vDSP_create_fftsetup(length, radix)
var cvector = makeComplexVector(arr: &dataarray!)
vDSP_fft_zrip(set_up!, &cvector, 1, length, 1)
for i in 0..<datalength!{
self.strength[String(i)] = sqrtf( cvector.realp[i]*cvector.realp[i] + cvector.imagp[i]*cvector.imagp[i] )
self.frequency[String(i)] = Float(i)/delta
}
createStrengthArray()
createFrequencyArray()
//fft setupを解放する
vDSP_destroy_fftsetup(set_up)
let elapsed = Date().timeIntervalSince(start)//時間計測終わり
print("FFTの処理時間:"+String(elapsed))//処理時間
}
//配列の長さををm倍してn分の1倍する
func fftExecute(mtimes m:Int, ndivide n:Int){
let start = Date()//時間計測
//窓関数の適用
applyWindowFunction()
if m > 1 {addZeroArray(m)}
delta = Float(dataarray!.count)/rate!
if n > 1 {resizeArray(n)}
let length = UInt(log2(Double(dataarray!.count*2)))
let radix:FFTRadix = 2
let set_up = vDSP_create_fftsetup(length, radix)
var cvector = makeComplexVector(arr: &dataarray!)
vDSP_fft_zrip(set_up!, &cvector, 1, length, 1)
for i in 0..<datalength!{
self.strength[String(i)] = sqrtf( cvector.realp[i]*cvector.realp[i] + cvector.imagp[i]*cvector.imagp[i] )
self.frequency[String(i)] = Float(i)/delta
}
createStrengthArray()
createFrequencyArray()
//fft setupを解放する
vDSP_destroy_fftsetup(set_up)
let elapsed = Date().timeIntervalSince(start)//時間計測終わり
print("FFTの処理時間:"+String(elapsed))//処理時間
}
}
追加情報
@OOPerさんからいただいた情報をもとに直しました。
修正した内容は
・プロパティにreal(imag)FloatPointerを追加
・deinit{・・・}を追加
・makeComplexVectorの修正
です。
class FFT {
var delta:Float = 0
var dataarray:[Float]?
var datalength:Int?
var rate:Float?
var strength = [String:Float]()
var frequency = [String:Float]()
var strengthArray = [Float]()
var frequencyArray = [Float]()
var realFloatPointer:UnsafeMutablePointer<Float>?
var imagFloatPointer:UnsafeMutablePointer<Float>?
init(_ vector:[Float],rate:Float){
datalength = vector.count
dataarray = vector
self.rate = rate
}
init(_ vector:[Int8],rate:Float){
datalength = vector.count
dataarray = vector.compactMap({return Float($0)/Float(Int8.max)})
self.rate = rate
}
init(_ vector:[Int16],rate:Float){
datalength = vector.count
dataarray = vector.compactMap({return Float($0)/Float(Int16.max)})
self.rate = rate
}
deinit {
realFloatPointer?.deallocate()
imagFloatPointer?.deallocate()
}
func makeComplexVector(arr:inout [Float]) -> DSPSplitComplex {
var empty:[Float] = Array<Float>(repeating:0.0,count:arr.count)
realFloatPointer = UnsafeMutablePointer<Float>.allocate(capacity: arr.count)
realFloatPointer!.initialize(from: &arr, count: arr.count)
imagFloatPointer = UnsafeMutablePointer<Float>.allocate(capacity: arr.count)
imagFloatPointer!.initialize(from: &empty, count: arr.count)
let cvector=DSPSplitComplex(realp:realFloatPointer!,imagp:imagFloatPointer!)
return cvector
}
func applyWindowFunction(){
// 窓関数
//dataarrayに窓関数を適用してdataarrayに再格納
var windowData = [Float](repeating:0,count: datalength!)
var windowOutput = [Float](repeating:0,count: datalength!)
vDSP_hann_window(&windowData, vDSP_Length(datalength!), Int32(0))
vDSP_vmul(&dataarray!, 1, &windowData, 1, &windowOutput, 1, vDSP_Length(datalength!))
dataarray = windowOutput
}
func createStrengthArray(){
strengthArray = []
for i in 0..<strength.count{
strengthArray.append(strength[String(i)]!)
}
}
func createFrequencyArray(){
frequencyArray = []
for i in 0..<frequency.count{
frequencyArray.append(frequency[String(i)]!)
}
}
func fftExecute(ntimes n:Int){
let start = Date()//時間計測
//窓関数の適用
applyWindowFunction()
if n > 1{addZeroArray(n)}
delta = Float(dataarray!.count)/rate!
let length = UInt(log2(Double(dataarray!.count*2)))
let radix:FFTRadix = 2
let set_up = vDSP_create_fftsetup(length, radix)
var cvector = makeComplexVector(arr: &dataarray!)
vDSP_fft_zrip(set_up!, &cvector, 1, length, 1)
for i in 0..<datalength!{
self.strength[String(i)] = sqrtf( cvector.realp[i]*cvector.realp[i] + cvector.imagp[i]*cvector.imagp[i] )
self.frequency[String(i)] = Float(i)/delta
if self.strength[String(i)] == Float.infinity{
self.strength[String(i)] = 0.0
}
}
createStrengthArray()
createFrequencyArray()
//fft setupを解放する
vDSP_destroy_fftsetup(set_up)
let elapsed = Date().timeIntervalSince(start)//時間計測終わり
print("FFTの処理時間:"+String(elapsed))//処理時間
}
//配列の長さををm倍してn分の1倍する
func fftExecute(mtimes m:Int, ndivide n:Int){
let start = Date()//時間計測
//窓関数の適用
applyWindowFunction()
if m > 1 {addZeroArray(m)}
delta = Float(dataarray!.count)/rate!
if n > 1 {resizeArray(n)}
let length = UInt(log2(Double(dataarray!.count*2)))
let radix:FFTRadix = 2
let set_up = vDSP_create_fftsetup(length, radix)
var cvector = makeComplexVector(arr: &dataarray!)
vDSP_fft_zrip(set_up!, &cvector, 1, length, 1)
for i in 0..<datalength!{
self.strength[String(i)] = sqrtf( cvector.realp[i]*cvector.realp[i] + cvector.imagp[i]*cvector.imagp[i] )
self.frequency[String(i)] = Float(i)/delta
}
createStrengthArray()
createFrequencyArray()
//fft setupを解放する
vDSP_destroy_fftsetup(set_up)
let elapsed = Date().timeIntervalSince(start)//時間計測終わり
print("FFTの処理時間:"+String(elapsed))//処理時間
}
//配列をn分の1のサイズにする
func resizeArray(_ n:Int){
var result = [Float]()
for i in 0 ..< dataarray!.count{
if i % n == 0 {
result.append(dataarray![i])
}
}
dataarray = result
}
//データに値が0の配列を足す n倍の長さになる
func addZeroArray(_ n:Int) {
let zeroArr = Array<Float>(repeating:0,count:dataarray!.count)
var sum = dataarray!
for _ in 0 ..< n-1 {
sum += zeroArr
}
dataarray = sum
}
//ピーク値の辞書を返す
func getPeakDict() -> [String:Float]{
var peakDict = [String:Float]()
for i in 0..<strength.count/2 {
if i > 0 && strength[String(i-1)]! < strength[String(i)]! && strength[String(i)]! > strength[String(i+1)]! {
peakDict[String(i)] = strength[String(i)]!
}
}
return peakDict
}
func getSortedStrength() -> ([String],[Float]){
let peakDict = getPeakDict()
let sorted_strength = peakDict.sorted(){$0.value > $1.value}
var rkey = [String]()
var rvalue = [Float]()
for (key,value) in sorted_strength {
rkey.append(key)
rvalue.append(value)
}
return (rkey,rvalue)
}
//引数はmaxの強度に対してどの割合まで取得するかのスレッショルド
func getPeakList(_ srd:Float,maxPeakNum mpn:Int) -> ([String],[Float],[Float]){
var sortedStrength = [Float]()
var sortedKey = [String]()
var resultStrength = [Float]()
var resultKey = [String]()
var resultFrequency = [Float]()
let tmp = getSortedStrength()
sortedKey = tmp.0
sortedStrength = tmp.1
let maxStrength = sortedStrength[0]
for i in 0..<mpn {
if sortedStrength[i] > maxStrength*srd {
resultStrength.append(sortedStrength[i])
resultFrequency.append(frequency[sortedKey[i]]!)
resultKey.append(sortedKey[i])
}
}
return (resultKey,resultFrequency,resultStrength)
}
//nthは周波数が低い方から何番目のピークかを選択する引数
//スタートは0
func selectPeakFrequency(_ srd:Float,maxPeakNum mpn:Int,nth n:Int) -> Float{
//結果にinfinityを含む場合は実施しない
if !strengthArray.contains(Float.infinity){
let peaklist = getPeakList(srd, maxPeakNum: mpn)
let keylist = peaklist.0
let freqlist = peaklist.1
let sortedFreqList = freqlist.sorted() { $0 < $1 }
var freq:Float = 0.0
if n > freqlist.count{
freq = sortedFreqList[freqlist.count]
}else{
freq = sortedFreqList[n]
}
var key = ""
for i in 0..<freqlist.count{
if freq == freqlist[i]{
key = keylist[i]
}
}
let util = FFTUtil()
freq = util.getPeakFreq(key, frequency, strength)
return freq
}
else{
return 0.0
}
}
func getConponent(_ n:Int) -> ([String:[String:Float]],[Int]){
/*
並び替えて上位n番目まで値を取得する
@note:return::[A:[B:C]] A:self.strength,self.frequency共通の通番のString変換 B:強さまたは周波数 C:値
*/
var peakList = [String:Float]()
for i in 0..<strength.count/2 {
if i > 0 && strength[String(i-1)]! < strength[String(i)]! && strength[String(i)]! > strength[String(i+1)]! {
peakList[String(i)] = strength[String(i)]!
}
}
var keyarray = [Int]()
let sorted_strength = peakList.sorted(){$0.value > $1.value}
var component = [String:[String:Float]]()
var i = 1
for (key,value) in sorted_strength {
keyarray.append(Int(key)!)
component[key] = [String:Float]()
component[key]!["strength"] = value
component[key]!["frequency"] = frequency[key]!
if i == n {
break
}
i += 1
}
return (component,keyarray)
}
}